Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
df23942f65 | ||
|
|
f154fcf8da | ||
|
|
7a16d803d3 | ||
|
|
73d3573198 | ||
|
|
9df8a957c7 | ||
|
|
baaf14c594 | ||
|
|
b3388f5bb7 | ||
|
|
3f1866f658 | ||
|
|
57f5414d79 | ||
|
|
0b174ef25a | ||
|
|
9cfa0f483d | ||
|
|
f28cba8388 | ||
|
|
ac5781d711 | ||
|
|
93fc8e463f |
47
.github/workflows/auto-test.yml
vendored
47
.github/workflows/auto-test.yml
vendored
@@ -6,7 +6,7 @@ concurrency:
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master, 1.23.X, 3.0.0]
|
||||
branches: [master, 1.23.X, 3.0.X]
|
||||
pull_request:
|
||||
permissions: {}
|
||||
|
||||
@@ -21,11 +21,7 @@ jobs:
|
||||
matrix:
|
||||
os: [macos-latest, ubuntu-22.04, windows-latest, ubuntu-22.04-arm]
|
||||
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
|
||||
node: [20, 24]
|
||||
# Also test non-LTS, but only on Ubuntu.
|
||||
include:
|
||||
- os: ubuntu-22.04
|
||||
node: 25
|
||||
node: [24, 25]
|
||||
|
||||
steps:
|
||||
- run: git config --global core.autocrlf false # Mainly for Windows
|
||||
@@ -55,37 +51,6 @@ jobs:
|
||||
HEADLESS_TEST: 1
|
||||
JUST_FOR_TEST: ${{ secrets.JUST_FOR_TEST }}
|
||||
|
||||
# As a lot of dev dependencies are not supported on ARMv7, we have to test it separately and just test if `npm ci --production` works
|
||||
armv7-simple-test:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
node: [20, 22]
|
||||
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
with: { persist-credentials: false }
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3.7.0
|
||||
with:
|
||||
platforms: linux/arm/v7
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@988b5a0280414f521da01fcc63a27aeeb4b104db # v3.6.1
|
||||
|
||||
- name: Test on ARMv7 using Docker with QEMU
|
||||
run: |
|
||||
docker run --rm --platform linux/arm/v7 \
|
||||
-v $PWD:/workspace \
|
||||
-w /workspace \
|
||||
arm32v7/node:${{ matrix.node }} \
|
||||
npm clean-install --no-fund --production
|
||||
|
||||
check-linters:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
@@ -103,10 +68,10 @@ jobs:
|
||||
path: node_modules
|
||||
key: node-modules-${{ runner.os }}-node${{ matrix.node }}-${{ hashFiles('**/package-lock.json') }}
|
||||
|
||||
- name: Use Node.js 20
|
||||
- name: Use Node.js 24
|
||||
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
|
||||
with:
|
||||
node-version: 20
|
||||
node-version: 24
|
||||
- run: npm clean-install --no-fund
|
||||
- run: npm run lint:prod
|
||||
|
||||
@@ -115,7 +80,7 @@ jobs:
|
||||
permissions:
|
||||
contents: read
|
||||
env:
|
||||
PLAYWRIGHT_VERSION: ~1.39.0
|
||||
PLAYWRIGHT_VERSION: ~1.56.0
|
||||
steps:
|
||||
- run: git config --global core.autocrlf false # Mainly for Windows
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
@@ -131,7 +96,7 @@ jobs:
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
|
||||
with:
|
||||
node-version: 22
|
||||
node-version: 24
|
||||
- run: npm clean-install --no-fund
|
||||
|
||||
- name: Rebuild native modules for ARM64
|
||||
|
||||
49
.github/workflows/build-docker-push.yml
vendored
49
.github/workflows/build-docker-push.yml
vendored
@@ -1,49 +0,0 @@
|
||||
name: Build Docker Push Image
|
||||
|
||||
on:
|
||||
schedule:
|
||||
# Runs at 2:00 AM UTC on the 1st of every month
|
||||
- cron: "0 2 1 * *"
|
||||
workflow_dispatch: # Allow manual trigger
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
build-docker-push:
|
||||
# Only run on the original repository, not on forks
|
||||
if: github.repository == 'louislam/uptime-kuma'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 120
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
with: { persist-credentials: false }
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3.7.0
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@988b5a0280414f521da01fcc63a27aeeb4b104db # v3.6.1
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Use Node.js 20
|
||||
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
|
||||
with:
|
||||
node-version: 20
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
|
||||
|
||||
- name: Install cross-env
|
||||
run: npm install -g cross-env
|
||||
|
||||
- name: Build and push Docker image
|
||||
working-directory: extra/uptime-kuma-push
|
||||
run: npm run build-docker
|
||||
1
.github/workflows/validate.yml
vendored
1
.github/workflows/validate.yml
vendored
@@ -7,6 +7,7 @@ on:
|
||||
branches:
|
||||
- master
|
||||
- 1.23.X
|
||||
- 3.0.X
|
||||
workflow_dispatch:
|
||||
permissions: {}
|
||||
|
||||
|
||||
26
CLAUDE.md
26
CLAUDE.md
@@ -1,26 +0,0 @@
|
||||
# CLAUDE.md
|
||||
|
||||
This is `guardrail` — part of BlackRoad OS.
|
||||
|
||||
> GuardRail — BlackRoad uptime monitoring. Forked from Uptime Kuma.
|
||||
|
||||
## Owner
|
||||
BlackRoad OS, Inc. — Proprietary. All rights reserved.
|
||||
|
||||
## AI Instructions
|
||||
- Part of BlackRoad ecosystem (615+ repos, 15 orgs)
|
||||
- Primary git: RoadCode (Gitea). GitHub is mirror.
|
||||
- All code proprietary unless marked otherwise
|
||||
- Brand: black bg, white text, gradient accents
|
||||
- Fonts: Space Grotesk, Inter, JetBrains Mono
|
||||
- Language: JavaScript
|
||||
|
||||
## Memory System
|
||||
- Check codex: `memory-codex.sh search "<problem>"`
|
||||
- Log actions: `memory-system.sh log <action> <entity> "<details>"`
|
||||
- Broadcast: `memory-til-broadcast.sh broadcast <cat> "<learning>"`
|
||||
|
||||
## Collaboration
|
||||
- Register: `memory-collaboration.sh register`
|
||||
- Claim: `memory-collaboration.sh claim "<task>"`
|
||||
- Board: `memory-collaboration.sh board`
|
||||
@@ -288,7 +288,7 @@ you can finally start the app. The goal is to make the Uptime Kuma installation
|
||||
as easy as installing a mobile app.
|
||||
|
||||
- Easy to install for non-Docker users
|
||||
- no native build dependency is needed (for `x86_64`/`armv7`/`arm64`)
|
||||
- no native build dependency is needed (for `x86_64`/`arm64`)
|
||||
- no extra configuration and
|
||||
- no extra effort required to get it running
|
||||
|
||||
@@ -469,7 +469,7 @@ We have a few procedures we follow. These are documented here:
|
||||
|
||||
- <details><summary><b>Set up a Docker Builder</b> (click to expand)</summary>
|
||||
<p>
|
||||
- amd64, armv7 using local.
|
||||
- amd64 using local.
|
||||
- arm64 using remote arm64 cpu, as the emulator is too slow and can no longer
|
||||
pass the `npm ci` command.
|
||||
1. Add the public key to the remote server.
|
||||
@@ -483,7 +483,7 @@ We have a few procedures we follow. These are documented here:
|
||||
3. Create a new builder.
|
||||
|
||||
```bash
|
||||
docker buildx create --name kuma-builder --platform linux/amd64,linux/arm/v7
|
||||
docker buildx create --name kuma-builder --platform linux/amd64
|
||||
docker buildx use kuma-builder
|
||||
docker buildx inspect --bootstrap
|
||||
```
|
||||
@@ -516,8 +516,7 @@ We have a few procedures we follow. These are documented here:
|
||||
These Items need to be checked:
|
||||
- [ ] Check all tags is fine on
|
||||
<https://hub.docker.com/r/louislam/uptime-kuma/tags>
|
||||
- [ ] Try the Docker image with tag 1.X.X (Clean install / amd64 / arm64 /
|
||||
armv7)
|
||||
- [ ] Try the Docker image with tag 1.X.X (Clean install / amd64 / arm64)
|
||||
- [ ] Try clean installation with Node.js
|
||||
|
||||
</p>
|
||||
|
||||
@@ -59,7 +59,7 @@ export default defineConfig({
|
||||
|
||||
// Run your local dev server before starting the tests.
|
||||
webServer: {
|
||||
command: `node extra/remove-playwright-test-data.js && cross-env NODE_ENV=development node server/server.js --port=${port} --data-dir=./data/playwright-test`,
|
||||
command: `node extra/remove-playwright-test-data.js && cross-env NODE_ENV=development node --import=tsx server/server.js --port=${port} --data-dir=./data/playwright-test`,
|
||||
url,
|
||||
reuseExistingServer: false,
|
||||
cwd: "../",
|
||||
|
||||
@@ -1,22 +1,15 @@
|
||||
############################################
|
||||
# Build in Golang
|
||||
# Run npm run build-healthcheck-armv7 in the host first, another it will be super slow where it is building the armv7 healthcheck
|
||||
############################################
|
||||
FROM golang:1-buster
|
||||
FROM golang:1-trixie
|
||||
WORKDIR /app
|
||||
ARG TARGETPLATFORM
|
||||
COPY ./extra/ ./extra/
|
||||
|
||||
## Switch to archive.debian.org
|
||||
RUN sed -i '/^deb/s/^/#/' /etc/apt/sources.list \
|
||||
&& echo "deb http://archive.debian.org/debian buster main contrib non-free" | tee -a /etc/apt/sources.list \
|
||||
&& echo "deb http://archive.debian.org/debian-security buster/updates main contrib non-free" | tee -a /etc/apt/sources.list \
|
||||
&& echo "deb http://archive.debian.org/debian buster-updates main contrib non-free" | tee -a /etc/apt/sources.list
|
||||
|
||||
# Compile healthcheck.go
|
||||
RUN apt update && \
|
||||
apt --yes --no-install-recommends install curl && \
|
||||
curl -sL https://deb.nodesource.com/setup_18.x | bash && \
|
||||
curl -sL https://deb.nodesource.com/setup_24.x | bash && \
|
||||
apt --yes --no-install-recommends install nodejs && \
|
||||
node ./extra/build-healthcheck.js $TARGETPLATFORM && \
|
||||
apt --yes remove nodejs
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Download Apprise deb package
|
||||
FROM node:22-bookworm-slim AS download-apprise
|
||||
FROM node:24-trixie-slim AS download-apprise
|
||||
WORKDIR /app
|
||||
COPY ./extra/download-apprise.mjs ./download-apprise.mjs
|
||||
RUN apt update && \
|
||||
@@ -9,7 +9,7 @@ RUN apt update && \
|
||||
|
||||
# Base Image (Slim)
|
||||
# If the image changed, the second stage image should be changed too
|
||||
FROM node:22-bookworm-slim AS base2-slim
|
||||
FROM node:24-trixie-slim AS base3-slim
|
||||
ARG TARGETPLATFORM
|
||||
|
||||
# Specify --no-install-recommends to skip unused dependencies, make the base much smaller!
|
||||
@@ -35,7 +35,6 @@ RUN apt update && \
|
||||
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.
|
||||
# python3-paho-mqtt (#4859)
|
||||
# TODO: no idea how to delete the deb file after installation as it becomes a layer already
|
||||
COPY --from=download-apprise /app/apprise.deb ./apprise.deb
|
||||
@@ -47,7 +46,7 @@ RUN apt update && \
|
||||
|
||||
# Install cloudflared
|
||||
RUN curl https://pkg.cloudflare.com/cloudflare-main.gpg --output /usr/share/keyrings/cloudflare-main.gpg && \
|
||||
echo 'deb [signed-by=/usr/share/keyrings/cloudflare-main.gpg] https://pkg.cloudflare.com/cloudflared bookworm main' | tee /etc/apt/sources.list.d/cloudflared.list && \
|
||||
echo 'deb [signed-by=/usr/share/keyrings/cloudflare-main.gpg] https://pkg.cloudflare.com/cloudflared any main' | tee /etc/apt/sources.list.d/cloudflared.list && \
|
||||
apt update && \
|
||||
apt install --yes --no-install-recommends cloudflared && \
|
||||
cloudflared version && \
|
||||
@@ -62,8 +61,7 @@ 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.
|
||||
# FROM base2-slim AS base2
|
||||
FROM louislam/uptime-kuma:base2-slim AS base2
|
||||
FROM louislam/uptime-kuma:base3-slim AS base3
|
||||
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 && \
|
||||
|
||||
@@ -3,7 +3,7 @@ version: "3.8"
|
||||
services:
|
||||
uptime-kuma:
|
||||
container_name: uptime-kuma-dev
|
||||
image: louislam/uptime-kuma:nightly2
|
||||
image: louislam/uptime-kuma:nightly3
|
||||
volumes:
|
||||
#- ./data:/app/data
|
||||
- ../server:/app/server
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
ARG BASE_IMAGE=louislam/uptime-kuma:base2
|
||||
ARG BASE_IMAGE=louislam/uptime-kuma:base3
|
||||
|
||||
############################################
|
||||
# Build in Golang
|
||||
# Run npm run build-healthcheck-armv7 in the host first, otherwise it will be super slow where it is building the armv7 healthcheck
|
||||
|
||||
# Check file: builder-go.dockerfile
|
||||
############################################
|
||||
FROM louislam/uptime-kuma:builder-go AS build_healthcheck
|
||||
FROM louislam/uptime-kuma:builder-go3 AS build_healthcheck
|
||||
|
||||
############################################
|
||||
# Build in Node.js
|
||||
############################################
|
||||
FROM louislam/uptime-kuma:base2 AS build
|
||||
FROM louislam/uptime-kuma:base3 AS build
|
||||
USER node
|
||||
WORKDIR /app
|
||||
|
||||
@@ -59,7 +59,7 @@ USER node
|
||||
############################################
|
||||
# Build an image for testing pr
|
||||
############################################
|
||||
FROM louislam/uptime-kuma:base2 AS pr-test2
|
||||
FROM louislam/uptime-kuma:base3 AS pr-test2
|
||||
WORKDIR /app
|
||||
ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=1
|
||||
|
||||
@@ -92,7 +92,7 @@ CMD ["npm", "run", "start-pr-test"]
|
||||
############################################
|
||||
# Upload the artifact to Github
|
||||
############################################
|
||||
FROM louislam/uptime-kuma:base2 AS upload-artifact
|
||||
FROM louislam/uptime-kuma:base3 AS upload-artifact
|
||||
WORKDIR /
|
||||
RUN apt update && \
|
||||
apt --yes install curl file
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
const childProcess = require("child_process");
|
||||
const fs = require("fs");
|
||||
const platform = process.argv[2];
|
||||
|
||||
if (!platform) {
|
||||
@@ -7,22 +6,5 @@ if (!platform) {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (platform === "linux/arm/v7") {
|
||||
console.log("Arch: armv7");
|
||||
if (fs.existsSync("./extra/healthcheck-armv7")) {
|
||||
fs.renameSync("./extra/healthcheck-armv7", "./extra/healthcheck");
|
||||
console.log("Already built in the host, skip.");
|
||||
process.exit(0);
|
||||
} else {
|
||||
console.log(
|
||||
"prebuilt not found, it will be slow! You should execute `npm run build-healthcheck-armv7` before build."
|
||||
);
|
||||
}
|
||||
} else {
|
||||
if (fs.existsSync("./extra/healthcheck-armv7")) {
|
||||
fs.rmSync("./extra/healthcheck-armv7");
|
||||
}
|
||||
}
|
||||
|
||||
const output = childProcess.execSync("go build -x -o ./extra/healthcheck ./extra/healthcheck.go").toString("utf8");
|
||||
console.log(output);
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
/*
|
||||
* ⚠️ ⚠️ ⚠️ ⚠️ Due to the weird issue in Portainer that the healthcheck script is still pointing to this script for unknown reason.
|
||||
* IT CANNOT BE DROPPED, even though it looks like it is not used.
|
||||
* See more: https://github.com/louislam/uptime-kuma/issues/2774#issuecomment-1429092359
|
||||
*
|
||||
* ⚠️ Deprecated: Changed to healthcheck.go, it will be deleted in the future.
|
||||
* This script should be run after a period of time (180s), because the server may need some time to prepare.
|
||||
*/
|
||||
const FBSD = /^freebsd/.test(process.platform);
|
||||
|
||||
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
|
||||
|
||||
let client;
|
||||
|
||||
const sslKey = process.env.UPTIME_KUMA_SSL_KEY || process.env.SSL_KEY || undefined;
|
||||
const sslCert = process.env.UPTIME_KUMA_SSL_CERT || process.env.SSL_CERT || undefined;
|
||||
|
||||
if (sslKey && sslCert) {
|
||||
client = require("https");
|
||||
} else {
|
||||
client = require("http");
|
||||
}
|
||||
|
||||
// If host is omitted, the server will accept connections on the unspecified IPv6 address (::) when IPv6 is available and the unspecified IPv4 address (0.0.0.0) otherwise.
|
||||
// Dual-stack support for (::)
|
||||
let hostname = process.env.UPTIME_KUMA_HOST;
|
||||
|
||||
// Also read HOST if not *BSD, as HOST is a system environment variable in FreeBSD
|
||||
if (!hostname && !FBSD) {
|
||||
hostname = process.env.HOST;
|
||||
}
|
||||
|
||||
const port = parseInt(process.env.UPTIME_KUMA_PORT || process.env.PORT || 3001);
|
||||
|
||||
let options = {
|
||||
host: hostname || "127.0.0.1",
|
||||
port: port,
|
||||
timeout: 28 * 1000,
|
||||
};
|
||||
|
||||
let request = client.request(options, (res) => {
|
||||
console.log(`Health Check OK [Res Code: ${res.statusCode}]`);
|
||||
if (res.statusCode === 302) {
|
||||
process.exit(0);
|
||||
} else {
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
|
||||
request.on("error", function (err) {
|
||||
console.error("Health Check ERROR");
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
request.end();
|
||||
@@ -61,14 +61,14 @@ if (!dryRun) {
|
||||
repoNames,
|
||||
["beta-slim-rootless", ver(version, "slim-rootless")],
|
||||
"rootless",
|
||||
"BASE_IMAGE=louislam/uptime-kuma:base2-slim"
|
||||
"BASE_IMAGE=louislam/uptime-kuma:base3-slim"
|
||||
);
|
||||
|
||||
// Build full image (rootless)
|
||||
buildImage(repoNames, ["beta-rootless", ver(version, "rootless")], "rootless");
|
||||
|
||||
// Build slim image
|
||||
buildImage(repoNames, ["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:base3-slim");
|
||||
|
||||
// Build full image
|
||||
buildImage(repoNames, ["beta", version], "release");
|
||||
|
||||
@@ -59,24 +59,24 @@ if (!dryRun) {
|
||||
// Build slim image (rootless)
|
||||
buildImage(
|
||||
repoNames,
|
||||
["2-slim-rootless", ver(version, "slim-rootless")],
|
||||
["3-slim-rootless", ver(version, "slim-rootless")],
|
||||
"rootless",
|
||||
"BASE_IMAGE=louislam/uptime-kuma:base2-slim"
|
||||
"BASE_IMAGE=louislam/uptime-kuma:base3-slim"
|
||||
);
|
||||
|
||||
// Build full image (rootless)
|
||||
buildImage(repoNames, ["2-rootless", ver(version, "rootless")], "rootless");
|
||||
buildImage(repoNames, ["3-rootless", ver(version, "rootless")], "rootless");
|
||||
|
||||
// Build slim image
|
||||
buildImage(
|
||||
repoNames,
|
||||
["next-slim", "2-slim", ver(version, "slim")],
|
||||
["next-slim", "3-slim", ver(version, "slim")],
|
||||
"release",
|
||||
"BASE_IMAGE=louislam/uptime-kuma:base2-slim"
|
||||
"BASE_IMAGE=louislam/uptime-kuma:base3-slim"
|
||||
);
|
||||
|
||||
// Build full image
|
||||
buildImage(repoNames, ["next", "2", version], "release");
|
||||
buildImage(repoNames, ["next", "3", version], "release");
|
||||
} else {
|
||||
console.log("Dry run mode - skipping image build and push.");
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@ export function buildImage(
|
||||
target,
|
||||
buildArgs = "",
|
||||
dockerfile = "docker/dockerfile",
|
||||
platform = "linux/amd64,linux/arm64,linux/arm/v7"
|
||||
platform = "linux/amd64,linux/arm64"
|
||||
) {
|
||||
let args = ["buildx", "build", "-f", dockerfile, "--platform", platform];
|
||||
|
||||
|
||||
4431
package-lock.json
generated
4431
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
55
package.json
55
package.json
@@ -1,13 +1,13 @@
|
||||
{
|
||||
"name": "uptime-kuma",
|
||||
"version": "2.2.1",
|
||||
"version": "3.0.0-beta.0",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/louislam/uptime-kuma.git"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 20.4.0"
|
||||
"node": ">= 24.0.0"
|
||||
},
|
||||
"scripts": {
|
||||
"lint:js": "eslint --ext \".js,.vue\" --ignore-path .gitignore .",
|
||||
@@ -20,31 +20,27 @@
|
||||
"lint:prod": "npm run lint:js-prod && npm run lint:style",
|
||||
"dev": "concurrently -k -r \"wait-on tcp:3000 && npm run start-server-dev \" \"npm run start-frontend-dev\"",
|
||||
"start-frontend-dev": "cross-env NODE_ENV=development vite --host --config ./config/vite.config.js",
|
||||
"start-frontend-devcontainer": "cross-env NODE_ENV=development DEVCONTAINER=1 vite --host --config ./config/vite.config.js",
|
||||
"start": "npm run start-server",
|
||||
"start-server": "node server/server.js",
|
||||
"start-server-dev": "cross-env NODE_ENV=development node server/server.js",
|
||||
"start-server-dev:watch": "cross-env NODE_ENV=development node --watch server/server.js",
|
||||
"start-server": "tsx server/server.js",
|
||||
"start-server-dev": "cross-env NODE_ENV=development tsx server/server.js",
|
||||
"start-server-dev:watch": "cross-env NODE_ENV=development tsx --watch server/server.js",
|
||||
"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": "node test/test-backend.mjs",
|
||||
"test-backend-22": "cross-env TEST_BACKEND=1 node --test --test-reporter=spec \"test/backend-test/**/*.js\"",
|
||||
"test-backend-20": "cross-env TEST_BACKEND=1 node --test --test-reporter=spec test/backend-test",
|
||||
"test-backend": "cross-env TEST_BACKEND=1 node --import=tsx --test --test-reporter=spec \"test/backend-test/**/*.js\"",
|
||||
"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",
|
||||
"playwright-show-report": "playwright show-report ./private/playwright-report",
|
||||
"tsc": "tsc --project ./tsconfig-backend.json",
|
||||
"vite-preview-dist": "vite preview --host --config ./config/vite.config.js",
|
||||
"build-docker-base": "docker buildx build -f docker/debian-base.dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:base2 --target base2 . --push",
|
||||
"build-docker-base-slim": "docker buildx build -f docker/debian-base.dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:base2-slim --target base2-slim . --push",
|
||||
"build-docker-builder-go": "docker buildx build -f docker/builder-go.dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:builder-go . --push",
|
||||
"build-docker-nightly-local": "npm run build && docker build -f docker/dockerfile -t louislam/uptime-kuma:nightly2 --target nightly .",
|
||||
"build-docker-pr-test": "docker buildx build -f docker/dockerfile --platform linux/amd64,linux/arm64 -t louislam/uptime-kuma:pr-test2 --target pr-test2 . --push",
|
||||
"build-docker-base": "docker buildx build -f docker/debian-base.dockerfile --platform linux/amd64,linux/arm64 -t louislam/uptime-kuma:base3 --target base3 . --push",
|
||||
"build-docker-base-slim": "docker buildx build -f docker/debian-base.dockerfile --platform linux/amd64,linux/arm64 -t louislam/uptime-kuma:base3-slim --target base3-slim . --push",
|
||||
"build-docker-builder-go": "docker buildx build -f docker/builder-go.dockerfile --platform linux/amd64,linux/arm64 -t louislam/uptime-kuma:builder-go3 . --push",
|
||||
"build-docker-nightly-local": "npm run build && docker build -f docker/dockerfile -t louislam/uptime-kuma:nightly3 --target nightly .",
|
||||
"build-docker-pr-test": "docker buildx build -f docker/dockerfile --platform linux/amd64,linux/arm64 -t louislam/uptime-kuma:pr-test3 --target pr-test3 . --push",
|
||||
"upload-artifacts": "node extra/release/upload-artifacts.mjs",
|
||||
"upload-artifacts-beta": "node extra/release/upload-artifacts-beta.mjs",
|
||||
"setup": "git checkout 2.2.1 && npm ci --omit dev --no-audit && npm run download-dist",
|
||||
"setup": "git checkout 2.2.0 && npm ci --omit dev --no-audit && npm run download-dist",
|
||||
"download-dist": "node extra/download-dist.js",
|
||||
"mark-as-nightly": "node extra/mark-as-nightly.js",
|
||||
"reset-password": "node extra/reset-password.js",
|
||||
@@ -60,11 +56,9 @@
|
||||
"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.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",
|
||||
"quick-run-nightly": "docker run --rm --env NODE_ENV=development -p 3001:3001 louislam/uptime-kuma:nightly2",
|
||||
"quick-run-nightly": "docker run --rm --env NODE_ENV=development -p 3001:3001 louislam/uptime-kuma:nightly3",
|
||||
"start-dev-container": "cd docker && docker-compose -f docker-compose-dev.yml up --force-recreate",
|
||||
"rebase-pr-to-1.23.X": "node extra/rebase-pr.js 1.23.X",
|
||||
"reset-migrate-aggregate-table-state": "node extra/reset-migrate-aggregate-table-state.js",
|
||||
"generate-changelog": "node ./extra/generate-changelog.mjs"
|
||||
},
|
||||
@@ -111,7 +105,7 @@
|
||||
"kafkajs": "~2.2.4",
|
||||
"knex": "~3.1.0",
|
||||
"limiter": "~2.1.0",
|
||||
"liquidjs": "~10.25.0",
|
||||
"liquidjs": "~10.24.0",
|
||||
"marked": "~14.1.4",
|
||||
"mitt": "~3.0.1",
|
||||
"mongodb": "~4.17.2",
|
||||
@@ -129,7 +123,7 @@
|
||||
"password-hash": "~1.2.2",
|
||||
"pg": "~8.11.6",
|
||||
"pg-connection-string": "~2.6.4",
|
||||
"playwright-core": "~1.39.0",
|
||||
"playwright-core": "~1.56.1",
|
||||
"prom-client": "~13.2.0",
|
||||
"prometheus-api-metrics": "~3.2.2",
|
||||
"promisify-child-process": "~4.1.2",
|
||||
@@ -150,7 +144,9 @@
|
||||
"tough-cookie": "~4.1.4",
|
||||
"validator": "~13.15.26",
|
||||
"web-push": "~3.6.7",
|
||||
"ws": "~8.19.0"
|
||||
"tsx": "~4.20.6",
|
||||
"ws": "~8.19.0",
|
||||
"zod": "~4.1.12"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@actions/github": "~6.0.1",
|
||||
@@ -158,7 +154,7 @@
|
||||
"@fortawesome/free-regular-svg-icons": "~5.15.4",
|
||||
"@fortawesome/free-solid-svg-icons": "~5.15.4",
|
||||
"@fortawesome/vue-fontawesome": "~3.1.3",
|
||||
"@playwright/test": "~1.39.0",
|
||||
"@playwright/test": "~1.56.1",
|
||||
"@popperjs/core": "~2.10.2",
|
||||
"@testcontainers/hivemq": "^10.28.0",
|
||||
"@testcontainers/mariadb": "^10.28.0",
|
||||
@@ -174,7 +170,7 @@
|
||||
"@vitejs/plugin-vue": "~5.0.5",
|
||||
"@vue/compiler-sfc": "~3.4.38",
|
||||
"@vuepic/vue-datepicker": "~3.4.8",
|
||||
"aedes": "~1.0.0",
|
||||
"aedes": "^0.46.3",
|
||||
"bootstrap": "5.1.3",
|
||||
"chart.js": "~4.2.1",
|
||||
"chartjs-adapter-dayjs-4": "~1.0.4",
|
||||
@@ -182,14 +178,16 @@
|
||||
"core-js": "~3.26.1",
|
||||
"cronstrue": "~2.24.0",
|
||||
"cross-env": "~7.0.3",
|
||||
"delay": "^5.0.0",
|
||||
"dns2": "~2.0.5",
|
||||
"dompurify": "~3.3.2",
|
||||
"dompurify": "~3.2.7",
|
||||
"eslint": "~8.14.0",
|
||||
"eslint-config-prettier": "^10.1.8",
|
||||
"eslint-plugin-jsdoc": "~46.4.6",
|
||||
"eslint-plugin-vue": "~8.7.1",
|
||||
"eslint-plugin-vue-scoped-css": "~2.7.2",
|
||||
"favico.js": "~0.3.10",
|
||||
"get-port-please": "^3.2.0",
|
||||
"node-ssh": "~13.1.0",
|
||||
"postcss-html": "~1.8.1",
|
||||
"postcss-rtlcss": "~5.7.1",
|
||||
@@ -223,5 +221,12 @@
|
||||
"vuedraggable": "~4.1.0",
|
||||
"wait-on": "^7.2.0",
|
||||
"whatwg-url": "~12.0.1"
|
||||
},
|
||||
"overrides": {
|
||||
"@aws-sdk/credential-providers": "npm:useless-module@1.0.0",
|
||||
"tedious": {
|
||||
"@azure/identity": "npm:useless-module@1.0.0",
|
||||
"@azure/keyvault-keys": "npm:useless-module@1.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -275,6 +275,7 @@ class Database {
|
||||
// See: https://github.com/knex/knex/issues/3176#issuecomment-3389054899
|
||||
min: 0,
|
||||
max: 20,
|
||||
propagateCreateError: false,
|
||||
acquireTimeoutMillis: acquireConnectionTimeout,
|
||||
afterCreate: (rawConn, done) => {
|
||||
this.initSQLite(rawConn, testMode)
|
||||
|
||||
@@ -2059,7 +2059,7 @@ class Monitor extends BeanModel {
|
||||
}
|
||||
|
||||
const parentActive = await Monitor.isParentActive(parent.id);
|
||||
return parent.active === 1 && parentActive;
|
||||
return parent.active && parentActive;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,249 +0,0 @@
|
||||
const NotificationProvider = require("./notification-provider");
|
||||
const axios = require("axios");
|
||||
const { DOWN, UP } = require("../../src/util");
|
||||
|
||||
class Fluxer extends NotificationProvider {
|
||||
name = "fluxer";
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
|
||||
const okMsg = "Sent Successfully.";
|
||||
|
||||
try {
|
||||
let config = this.getAxiosConfigWithProxy({});
|
||||
const fluxerDisplayName = notification.fluxerUsername || "Uptime Kuma";
|
||||
const webhookUrl = new URL(notification.fluxerWebhookUrl);
|
||||
|
||||
// Check if the webhook has an avatar
|
||||
let webhookHasAvatar = true;
|
||||
try {
|
||||
const webhookInfo = await axios.get(webhookUrl.toString(), config);
|
||||
webhookHasAvatar = !!webhookInfo.data.avatar;
|
||||
} catch (e) {
|
||||
// If we can't verify, we assume he has an avatar to avoid forcing the default avatar
|
||||
webhookHasAvatar = true;
|
||||
}
|
||||
|
||||
const messageFormat =
|
||||
notification.fluxerMessageFormat || (notification.fluxerUseMessageTemplate ? "custom" : "normal");
|
||||
|
||||
// If heartbeatJSON is null, assume we're testing.
|
||||
if (heartbeatJSON == null) {
|
||||
let content = msg;
|
||||
if (messageFormat === "minimalist") {
|
||||
content = "Test: " + msg;
|
||||
} else if (messageFormat === "custom") {
|
||||
const customMessage = notification.fluxerMessageTemplate?.trim() || "";
|
||||
if (customMessage !== "") {
|
||||
content = await this.renderTemplate(customMessage, msg, monitorJSON, heartbeatJSON);
|
||||
}
|
||||
}
|
||||
let fluxertestdata = {
|
||||
username: fluxerDisplayName,
|
||||
content: content,
|
||||
};
|
||||
if (!webhookHasAvatar) {
|
||||
fluxertestdata.avatar_url = "https://github.com/louislam/uptime-kuma/raw/master/public/icon.png";
|
||||
}
|
||||
await axios.post(webhookUrl.toString(), fluxertestdata, config);
|
||||
return okMsg;
|
||||
}
|
||||
|
||||
// If heartbeatJSON is not null, we go into the normal alerting loop.
|
||||
let addess = this.extractAddress(monitorJSON);
|
||||
|
||||
// Minimalist: status + name only (is down / is up; no "back up" — may be first trigger)
|
||||
if (messageFormat === "minimalist") {
|
||||
const content =
|
||||
heartbeatJSON["status"] === DOWN
|
||||
? "🔴 " + monitorJSON["name"] + " is down."
|
||||
: "🟢 " + monitorJSON["name"] + " is up.";
|
||||
let payload = {
|
||||
username: fluxerDisplayName,
|
||||
content: content,
|
||||
};
|
||||
if (!webhookHasAvatar) {
|
||||
payload.avatar_url = "https://github.com/louislam/uptime-kuma/raw/master/public/icon.png";
|
||||
}
|
||||
|
||||
await axios.post(webhookUrl.toString(), payload, config);
|
||||
return okMsg;
|
||||
}
|
||||
|
||||
// Custom template: send only content (no embeds)
|
||||
const useCustomTemplate =
|
||||
messageFormat === "custom" && (notification.fluxerMessageTemplate?.trim() || "") !== "";
|
||||
if (useCustomTemplate) {
|
||||
const content = await this.renderTemplate(
|
||||
notification.fluxerMessageTemplate.trim(),
|
||||
msg,
|
||||
monitorJSON,
|
||||
heartbeatJSON
|
||||
);
|
||||
let payload = {
|
||||
username: fluxerDisplayName,
|
||||
content: content,
|
||||
};
|
||||
if (!webhookHasAvatar) {
|
||||
payload.avatar_url = "https://github.com/louislam/uptime-kuma/raw/master/public/icon.png";
|
||||
}
|
||||
|
||||
await axios.post(webhookUrl.toString(), payload, config);
|
||||
return okMsg;
|
||||
}
|
||||
|
||||
if (heartbeatJSON["status"] === DOWN) {
|
||||
const wentOfflineTimestamp = Math.floor(new Date(heartbeatJSON["time"]).getTime() / 1000);
|
||||
|
||||
let fluxerdowndata = {
|
||||
username: fluxerDisplayName,
|
||||
embeds: [
|
||||
{
|
||||
title: "❌ Your service " + monitorJSON["name"] + " went down. ❌",
|
||||
color: 16711680,
|
||||
fields: [
|
||||
{
|
||||
name: "Service Name",
|
||||
value: monitorJSON["name"],
|
||||
},
|
||||
...(!notification.disableUrl && addess
|
||||
? [
|
||||
{
|
||||
name: monitorJSON["type"] === "push" ? "Service Type" : "Service URL",
|
||||
value: addess,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
{
|
||||
name: "Went Offline",
|
||||
// F for full date/time
|
||||
value: `<t:${wentOfflineTimestamp}:F>`,
|
||||
},
|
||||
{
|
||||
name: `Time (${heartbeatJSON["timezone"]})`,
|
||||
value: heartbeatJSON["localDateTime"],
|
||||
},
|
||||
{
|
||||
name: "Error",
|
||||
value: heartbeatJSON["msg"] == null ? "N/A" : heartbeatJSON["msg"],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
if (!webhookHasAvatar) {
|
||||
fluxerdowndata.avatar_url = "https://github.com/louislam/uptime-kuma/raw/master/public/icon.png";
|
||||
}
|
||||
if (notification.fluxerPrefixMessage) {
|
||||
fluxerdowndata.content = notification.fluxerPrefixMessage;
|
||||
}
|
||||
|
||||
await axios.post(webhookUrl.toString(), fluxerdowndata, config);
|
||||
return okMsg;
|
||||
} else if (heartbeatJSON["status"] === UP) {
|
||||
const backOnlineTimestamp = Math.floor(new Date(heartbeatJSON["time"]).getTime() / 1000);
|
||||
let downtimeDuration = null;
|
||||
let wentOfflineTimestamp = null;
|
||||
if (heartbeatJSON["lastDownTime"]) {
|
||||
wentOfflineTimestamp = Math.floor(new Date(heartbeatJSON["lastDownTime"]).getTime() / 1000);
|
||||
downtimeDuration = this.formatDuration(backOnlineTimestamp - wentOfflineTimestamp);
|
||||
}
|
||||
|
||||
let fluxerupdata = {
|
||||
username: fluxerDisplayName,
|
||||
embeds: [
|
||||
{
|
||||
title: "✅ Your service " + monitorJSON["name"] + " is up! ✅",
|
||||
color: 65280,
|
||||
fields: [
|
||||
{
|
||||
name: "Service Name",
|
||||
value: monitorJSON["name"],
|
||||
},
|
||||
...(!notification.disableUrl && addess
|
||||
? [
|
||||
{
|
||||
name: monitorJSON["type"] === "push" ? "Service Type" : "Service URL",
|
||||
value: addess,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(wentOfflineTimestamp
|
||||
? [
|
||||
{
|
||||
name: "Went Offline",
|
||||
// F for full date/time
|
||||
value: `<t:${wentOfflineTimestamp}:F>`,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(downtimeDuration
|
||||
? [
|
||||
{
|
||||
name: "Downtime Duration",
|
||||
value: downtimeDuration,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
// Show server timezone for parity with the DOWN notification embed
|
||||
{
|
||||
name: `Time (${heartbeatJSON["timezone"]})`,
|
||||
value: heartbeatJSON["localDateTime"],
|
||||
},
|
||||
...(heartbeatJSON["ping"] != null
|
||||
? [
|
||||
{
|
||||
name: "Ping",
|
||||
value: heartbeatJSON["ping"] + " ms",
|
||||
},
|
||||
]
|
||||
: []),
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
if (!webhookHasAvatar) {
|
||||
fluxerupdata.avatar_url = "https://github.com/louislam/uptime-kuma/raw/master/public/icon.png";
|
||||
}
|
||||
if (notification.fluxerPrefixMessage) {
|
||||
fluxerupdata.content = notification.fluxerPrefixMessage;
|
||||
}
|
||||
|
||||
await axios.post(webhookUrl.toString(), fluxerupdata, config);
|
||||
return okMsg;
|
||||
}
|
||||
} catch (error) {
|
||||
this.throwGeneralAxiosError(error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Format duration as human-readable string (e.g., "1h 23m", "45m 30s")
|
||||
* TODO: Update below to `Intl.DurationFormat("en", { style: "short" }).format(duration)` once we are on a newer node version
|
||||
* @param {number} timeInSeconds The time in seconds to format a duration for
|
||||
* @returns {string} The formatted duration
|
||||
*/
|
||||
formatDuration(timeInSeconds) {
|
||||
const hours = Math.floor(timeInSeconds / 3600);
|
||||
const minutes = Math.floor((timeInSeconds % 3600) / 60);
|
||||
const seconds = timeInSeconds % 60;
|
||||
|
||||
const durationParts = [];
|
||||
if (hours > 0) {
|
||||
durationParts.push(`${hours}h`);
|
||||
}
|
||||
if (minutes > 0) {
|
||||
durationParts.push(`${minutes}m`);
|
||||
}
|
||||
if (seconds > 0 && hours === 0) {
|
||||
// Only show seconds if less than an hour
|
||||
durationParts.push(`${seconds}s`);
|
||||
}
|
||||
|
||||
return durationParts.length > 0 ? durationParts.join(" ") : "0s";
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Fluxer;
|
||||
@@ -12,7 +12,6 @@ const CallMeBot = require("./notification-providers/call-me-bot");
|
||||
const SMSC = require("./notification-providers/smsc");
|
||||
const DingDing = require("./notification-providers/dingding");
|
||||
const Discord = require("./notification-providers/discord");
|
||||
const Fluxer = require("./notification-providers/fluxer");
|
||||
const Elks = require("./notification-providers/46elks");
|
||||
const Feishu = require("./notification-providers/feishu");
|
||||
const Notifery = require("./notification-providers/notifery");
|
||||
@@ -118,7 +117,6 @@ class Notification {
|
||||
new SMSC(),
|
||||
new DingDing(),
|
||||
new Discord(),
|
||||
new Fluxer(),
|
||||
new Elks(),
|
||||
new Feishu(),
|
||||
new FreeMobile(),
|
||||
|
||||
@@ -52,6 +52,10 @@ router.all("/api/push/:pushToken", async (request, response) => {
|
||||
let statusString = request.query.status || "up";
|
||||
const statusFromParam = statusString === "up" ? UP : DOWN;
|
||||
|
||||
// Check if status=down was explicitly provided (not defaulting to "up")
|
||||
// When explicitly pushing down, bypass retry logic and go directly to DOWN
|
||||
const isExplicitDown = request.query.status === "down";
|
||||
|
||||
// Validate ping value - max 100 billion ms (~3.17 years)
|
||||
// Fits safely in both BIGINT and FLOAT(20,2)
|
||||
const MAX_PING_MS = 100000000000;
|
||||
@@ -85,7 +89,14 @@ router.all("/api/push/:pushToken", async (request, response) => {
|
||||
msg = "Monitor under maintenance";
|
||||
bean.status = MAINTENANCE;
|
||||
} else {
|
||||
determineStatus(statusFromParam, previousHeartbeat, monitor.maxretries, monitor.isUpsideDown(), bean);
|
||||
determineStatus(
|
||||
statusFromParam,
|
||||
previousHeartbeat,
|
||||
monitor.maxretries,
|
||||
monitor.isUpsideDown(),
|
||||
bean,
|
||||
isExplicitDown
|
||||
);
|
||||
}
|
||||
|
||||
// Calculate uptime
|
||||
@@ -129,7 +140,7 @@ router.all("/api/push/:pushToken", async (request, response) => {
|
||||
Monitor.sendStats(io, monitor.id, monitor.user_id);
|
||||
|
||||
try {
|
||||
new Prometheus(monitor, await monitor.getTags()).update(bean, undefined);
|
||||
new Prometheus(monitor, []).update(bean, undefined);
|
||||
} catch (e) {
|
||||
log.error("prometheus", "Please submit an issue to our GitHub repo. Prometheus update error: ", e.message);
|
||||
}
|
||||
@@ -571,13 +582,21 @@ router.get("/api/badge/:id/response", cache("5 minutes"), async (request, respon
|
||||
* @param {number} maxretries - The maximum number of retries allowed.
|
||||
* @param {boolean} isUpsideDown - Indicates if the monitor is upside down.
|
||||
* @param {object} bean - The new heartbeat object.
|
||||
* @param {boolean} isExplicitDown - If status=down was explicitly pushed, bypass retries.
|
||||
* @returns {void}
|
||||
*/
|
||||
function determineStatus(status, previousHeartbeat, maxretries, isUpsideDown, bean) {
|
||||
function determineStatus(status, previousHeartbeat, maxretries, isUpsideDown, bean, isExplicitDown) {
|
||||
if (isUpsideDown) {
|
||||
status = flipStatus(status);
|
||||
}
|
||||
|
||||
// If status=down was explicitly pushed, bypass retry logic and go directly to DOWN
|
||||
if (isExplicitDown && status === DOWN) {
|
||||
bean.retries = 0;
|
||||
bean.status = DOWN;
|
||||
return;
|
||||
}
|
||||
|
||||
if (previousHeartbeat) {
|
||||
if (previousHeartbeat.status === UP && status === DOWN) {
|
||||
// Going Down
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
* node "server/server.js"
|
||||
* DO NOT require("./server") in other modules, it likely creates circular dependency!
|
||||
*/
|
||||
import { genSecret, getRandomInt, isDev, log, sleep } from "../src/util";
|
||||
|
||||
console.log("Welcome to Uptime Kuma");
|
||||
|
||||
// As the log function need to use dayjs, it should be very top
|
||||
@@ -46,7 +48,6 @@ if (!semver.satisfies(nodeVersion, requiredNodeVersions)) {
|
||||
}
|
||||
|
||||
const args = require("args-parser")(process.argv);
|
||||
const { sleep, log, getRandomInt, genSecret, isDev } = require("../src/util");
|
||||
const config = require("./config");
|
||||
|
||||
process.title = "uptime-kuma";
|
||||
|
||||
@@ -215,7 +215,6 @@ export default {
|
||||
bale: "Bale",
|
||||
Bitrix24: "Bitrix24",
|
||||
discord: "Discord",
|
||||
fluxer: "Fluxer",
|
||||
GoogleChat: "Google Chat (Google Workspace)",
|
||||
gorush: "Gorush",
|
||||
gotify: "Gotify",
|
||||
|
||||
@@ -310,15 +310,15 @@ export default {
|
||||
// Show ping values if it was up in this period
|
||||
avgPingData.push({
|
||||
x,
|
||||
y: datapoint.up > 0 && datapoint.avgPing != null ? datapoint.avgPing : null,
|
||||
y: datapoint.up > 0 && datapoint.avgPing > 0 ? datapoint.avgPing : null,
|
||||
});
|
||||
minPingData.push({
|
||||
x,
|
||||
y: datapoint.up > 0 && datapoint.avgPing != null ? datapoint.minPing : null,
|
||||
y: datapoint.up > 0 && datapoint.avgPing > 0 ? datapoint.minPing : null,
|
||||
});
|
||||
maxPingData.push({
|
||||
x,
|
||||
y: datapoint.up > 0 && datapoint.avgPing != null ? datapoint.maxPing : null,
|
||||
y: datapoint.up > 0 && datapoint.avgPing > 0 ? datapoint.maxPing : null,
|
||||
});
|
||||
downData.push({
|
||||
x,
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
<template>
|
||||
<div class="mb-3">
|
||||
<label for="discord-webhook-url" class="form-label">{{ $t("Discord Webhook URL") }}</label>
|
||||
<HiddenInput
|
||||
<input
|
||||
id="discord-webhook-url"
|
||||
v-model="$parent.notification.discordWebhookUrl"
|
||||
type="text"
|
||||
class="form-control"
|
||||
required
|
||||
autocomplete="false"
|
||||
/>
|
||||
@@ -143,13 +144,11 @@
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import HiddenInput from "../HiddenInput.vue";
|
||||
import TemplatedTextarea from "../TemplatedTextarea.vue";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
TemplatedTextarea,
|
||||
HiddenInput,
|
||||
},
|
||||
mounted() {
|
||||
if (!this.$parent.notification.discordChannelType) {
|
||||
|
||||
@@ -1,87 +0,0 @@
|
||||
<template>
|
||||
<div class="mb-3">
|
||||
<label for="fluxer-webhook-url" class="form-label">{{ $t("Fluxer Webhook URL") }}</label>
|
||||
<HiddenInput
|
||||
id="fluxer-webhook-url"
|
||||
v-model="$parent.notification.fluxerWebhookUrl"
|
||||
type="url"
|
||||
required
|
||||
autocomplete="false"
|
||||
/>
|
||||
<div class="form-text">
|
||||
{{ $t("wayToGetFluxerURL") }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="fluxer-username" class="form-label">{{ $t("Bot Display Name") }}</label>
|
||||
<input
|
||||
id="fluxer-username"
|
||||
v-model="$parent.notification.fluxerUsername"
|
||||
type="text"
|
||||
class="form-control"
|
||||
autocomplete="false"
|
||||
:placeholder="$root.appName"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="fluxer-prefix-message" class="form-label">{{ $t("Prefix Custom Message") }}</label>
|
||||
<input
|
||||
id="fluxer-prefix-message"
|
||||
v-model="$parent.notification.fluxerPrefixMessage"
|
||||
type="text"
|
||||
class="form-control"
|
||||
autocomplete="false"
|
||||
:placeholder="$t('Hello @everyone is...')"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="fluxer-message-format" class="form-label">{{ $t("fluxerMessageFormat") }}</label>
|
||||
<select id="fluxer-message-format" v-model="$parent.notification.fluxerMessageFormat" class="form-select">
|
||||
<option value="normal">{{ $t("fluxerMessageFormatNormal") }}</option>
|
||||
<option value="minimalist">{{ $t("fluxerMessageFormatMinimalist") }}</option>
|
||||
<option value="custom">{{ $t("fluxerMessageFormatCustom") }}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div v-show="$parent.notification.fluxerMessageFormat === 'custom'">
|
||||
<div class="mb-3">
|
||||
<label for="fluxer-message-template" class="form-label">{{ $t("fluxerMessageTemplate") }}</label>
|
||||
<TemplatedTextarea
|
||||
id="fluxer-message-template"
|
||||
v-model="$parent.notification.fluxerMessageTemplate"
|
||||
:required="false"
|
||||
placeholder=""
|
||||
></TemplatedTextarea>
|
||||
<div class="form-text">{{ $t("fluxerUseMessageTemplateDescription") }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import HiddenInput from "../HiddenInput.vue";
|
||||
import TemplatedTextarea from "../TemplatedTextarea.vue";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
TemplatedTextarea,
|
||||
HiddenInput,
|
||||
},
|
||||
mounted() {
|
||||
if (!this.$parent.notification.fluxerChannelType) {
|
||||
this.$parent.notification.fluxerChannelType = "channel";
|
||||
}
|
||||
if (this.$parent.notification.disableUrl === undefined) {
|
||||
this.$parent.notification.disableUrl = false;
|
||||
}
|
||||
// Message format: default "normal"; migrate from old checkbox
|
||||
if (typeof this.$parent.notification.fluxerMessageFormat === "undefined") {
|
||||
const hadCustom =
|
||||
this.$parent.notification.fluxerUseMessageTemplate === true ||
|
||||
!!this.$parent.notification.fluxerMessageTemplate?.trim();
|
||||
this.$parent.notification.fluxerMessageFormat = hadCustom ? "custom" : "normal";
|
||||
}
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -38,7 +38,7 @@
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="notificationService" class="form-label">{{ $t("Notification Action") }}</label>
|
||||
<label for="notificationService" class="form-label">{{ $t("Notification Service") }}</label>
|
||||
<input
|
||||
id="notificationService"
|
||||
v-model="$parent.notification.notificationService"
|
||||
@@ -48,7 +48,13 @@
|
||||
/>
|
||||
|
||||
<div class="form-text">
|
||||
<p>{{ $t("homeAssistantNotificationActionHelptext") }}</p>
|
||||
<p>
|
||||
{{
|
||||
$t(
|
||||
'A list of Notification Services can be found in Home Assistant under "Developer Tools > Services" search for "notification" to find your device/phone name.'
|
||||
)
|
||||
}}
|
||||
</p>
|
||||
<p>{{ $t("Automations can optionally be triggered in Home Assistant:") }}</p>
|
||||
<p>
|
||||
{{ $t("Trigger type:") }}
|
||||
|
||||
@@ -11,7 +11,6 @@ import CallMeBot from "./CallMeBot.vue";
|
||||
import SMSC from "./SMSC.vue";
|
||||
import DingDing from "./DingDing.vue";
|
||||
import Discord from "./Discord.vue";
|
||||
import Fluxer from "./Fluxer.vue";
|
||||
import Elks from "./46elks.vue";
|
||||
import Feishu from "./Feishu.vue";
|
||||
import FreeMobile from "./FreeMobile.vue";
|
||||
@@ -106,7 +105,6 @@ const NotificationFormList = {
|
||||
smsir: SMSIR,
|
||||
DingDing: DingDing,
|
||||
discord: Discord,
|
||||
fluxer: Fluxer,
|
||||
Elks: Elks,
|
||||
Feishu: Feishu,
|
||||
FreeMobile: FreeMobile,
|
||||
|
||||
@@ -1611,14 +1611,5 @@
|
||||
"360messengerWayToGetUrlAndToken": "Можете да получите вашия API ключ за 360messenger от {0}.",
|
||||
"360messengerWayToWriteRecipient": "Въведете един или повече телефонни номера в международен формат без водещ плюс (напр. {0}). Разделете отделните номера със запетая.",
|
||||
"GlobalpingMultipleLocationsError": "Не се поддържат множество местоположения, моля, използвайте едно местоположение за всеки монитор.",
|
||||
"GlobalpingLocationDescription": "Полето за местоположение приема континенти, държави, региони, градове, ASN, интернет доставчици или облачни региони. Можете да комбинирате филтри с {plus} (напр. {amazonPlusGermany} или {comcastPlusCalifornia}). Ако латентността е важен показател, използвайте филтри, за да стесните местоположението до малък регион, за да избегнете пикове, и за по-добра стабилност задайте филтъра {datacenter}. {fullDocs}.",
|
||||
"fluxerMessageFormat": "Формат на съобщението",
|
||||
"fluxerMessageFormatNormal": "Нормално (с вграден rich)",
|
||||
"fluxerMessageFormatCustom": "Персонализиран шаблон",
|
||||
"fluxerUseMessageTemplate": "Използвай персонализиран шаблон за съобщение",
|
||||
"fluxerMessageTemplate": "Шаблон за съобщение",
|
||||
"Fluxer Webhook URL": "Fluxer URL адрес за уебкука",
|
||||
"fluxerMessageFormatMinimalist": "Минималистичен (кратък статус)",
|
||||
"fluxerUseMessageTemplateDescription": "Ако е активирано, съобщението ще бъде изпратено с помощта на персонализиран шаблон (LiquidJS). Оставете празно, за да използвате Uptime Kuma формат, който е по подразбиране.",
|
||||
"wayToGetFluxerURL": "Можете да получите, като отидете в настройките на целевия канал > Уеб куки > Създаване на уеб кука > Копиране на URL адрес на уеб кука."
|
||||
"GlobalpingLocationDescription": "Полето за местоположение приема континенти, държави, региони, градове, ASN, интернет доставчици или облачни региони. Можете да комбинирате филтри с {plus} (напр. {amazonPlusGermany} или {comcastPlusCalifornia}). Ако латентността е важен показател, използвайте филтри, за да стесните местоположението до малък регион, за да избегнете пикове, и за по-добра стабилност задайте филтъра {datacenter}. {fullDocs}."
|
||||
}
|
||||
|
||||
@@ -514,9 +514,9 @@
|
||||
"Home Assistant URL": "Home Assistant URL",
|
||||
"Long-Lived Access Token": "Long-Lived Access Token",
|
||||
"Long-Lived Access Token can be created by clicking on your profile name (bottom left) and scrolling to the bottom then click Create Token. ": "Long-Lived Access Token can be created by clicking on your profile name (bottom left) and scrolling to the bottom then click Create Token.",
|
||||
"Notification Action": "Notification Action",
|
||||
"Notification Service": "Notification Service",
|
||||
"default: notify all devices": "default: notify all devices",
|
||||
"homeAssistantNotificationActionHelptext": "A list of Notification Actions can be found in Home Assistant under \"Settings > Developer Tools > Actions\". Search for \"notify\" to find your actions. Enter only the part after \"notify.\", e.g. for the action \"notify.mobile_app_xyz\" enter \"mobile_app_xyz\". For built-in mobile notifications, look for \"Send a notification via mobile_app_xyz\" (not \"Send a notification\").",
|
||||
"A list of Notification Services can be found in Home Assistant under \"Developer Tools > Services\" search for \"notification\" to find your device/phone name.": "A list of Notification Services can be found in Home Assistant under \"Developer Tools > Services\" search for \"notification\" to find your device/phone name.",
|
||||
"Automations can optionally be triggered in Home Assistant:": "Automations can optionally be triggered in Home Assistant:",
|
||||
"Trigger type:": "Trigger type:",
|
||||
"Event type:": "Event type:",
|
||||
@@ -1358,15 +1358,6 @@
|
||||
"discordUseMessageTemplate": "Use custom message template",
|
||||
"discordUseMessageTemplateDescription": "If enabled, the message will be sent using a custom template (LiquidJS). Leave blank to use the default Uptime Kuma format.",
|
||||
"discordMessageTemplate": "Message Template",
|
||||
"fluxerMessageFormat": "Message Format",
|
||||
"fluxerMessageFormatNormal": "Normal (rich embeds)",
|
||||
"fluxerMessageFormatMinimalist": "Minimalist (short status)",
|
||||
"fluxerMessageFormatCustom": "Custom template",
|
||||
"fluxerUseMessageTemplate": "Use custom message template",
|
||||
"fluxerUseMessageTemplateDescription": "If enabled, the message will be sent using a custom template (LiquidJS). Leave blank to use the default Uptime Kuma format.",
|
||||
"fluxerMessageTemplate": "Message Template",
|
||||
"Fluxer Webhook URL": "Fluxer Webhook URL",
|
||||
"wayToGetFluxerURL": "You can get this by going to the target channel's settings > Webhooks > Create Webhook > Copy Webhook URL.",
|
||||
"Ip Family": "IP Family",
|
||||
"ipFamilyDescriptionAutoSelect": "Uses the {happyEyeballs} for determining the IP family.",
|
||||
"Happy Eyeballs algorithm": "Happy Eyeballs algorithm",
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
{}
|
||||
@@ -155,7 +155,7 @@
|
||||
"Skip existing": "Omitir existente",
|
||||
"Overwrite": "Sobrescribir",
|
||||
"Options": "Opciones",
|
||||
"Keep both": "Mantener ambos",
|
||||
"Keep both": "Manténer ambos",
|
||||
"Tags": "Etiquetas",
|
||||
"Add New below or Select...": "Agregar nuevo a continuación o seleccionar…",
|
||||
"Tag with this name already exist.": "Una etiqueta con este nombre ya existe.",
|
||||
@@ -510,7 +510,7 @@
|
||||
"Octopush API Version": "Versión API Octopush",
|
||||
"From Name/Number": "De Nombre/Número",
|
||||
"Recipient Number": "Número de Destinatario",
|
||||
"Long-Lived Access Token can be created by clicking on your profile name (bottom left) and scrolling to the bottom then click Create Token. ": "El token de acceso de larga duración puede crearse haciendo clic en el nombre de su perfil (abajo a la izquierda), desplazándose hasta la parte inferior y luego haciendo clic en Crear token.",
|
||||
"Long-Lived Access Token can be created by clicking on your profile name (bottom left) and scrolling to the bottom then click Create Token. ": "El token de acceso de larga duración se puede crear haciendo clic en el nombre de su perfil (abajo a la izquierda) y desplazándose hasta la parte inferior y luego haciendo clic en Crear token. ",
|
||||
"backupOutdatedWarning": "Obsoleto: dado que se agregaron muchas funciones y esta función de copia de seguridad no se mantiene desde hace un tiempo, no puede generar ni restaurar una copia de seguridad completa.",
|
||||
"Optional": "Opcional",
|
||||
"loadingError": "No se pueden obtener los datos, inténtelo de nuevo más tarde.",
|
||||
@@ -597,7 +597,7 @@
|
||||
"checkPrice": "Consultar {0} precios:",
|
||||
"apiCredentials": "Credenciales de API",
|
||||
"Check octopush prices": "Consulta los precios de octopush {0}.",
|
||||
"octopushPhoneNumber": "Número de teléfono (en formato internacional, ejemplo: +33612345678)",
|
||||
"octopushPhoneNumber": "Número de teléfono (en formato internacional, ejemplo: +33612345678) ",
|
||||
"octopushSMSSender": "Nombre de Remitente del SMS: 3-11 caracteres alfanuméricos y espacio (a-zA-Z0-9)",
|
||||
"LunaSea Device ID": "ID Dispositivo LunaSea",
|
||||
"goAlert": "GoAlert",
|
||||
@@ -649,7 +649,7 @@
|
||||
"alertaEnvironment": "Entorno",
|
||||
"PushDeer Key": "Key de PushDeer",
|
||||
"onebotSafetyTips": "Por seguridad, deberías colocara el token de acceso",
|
||||
"wayToGetClickSendSMSToken": "Puedes obtener Usuario de API y llave de API {aquí}.",
|
||||
"wayToGetClickSendSMSToken": "Puedes obtener Nombre de Usuario de la API y la llave {aquí}.",
|
||||
"Apprise URL": "URL Apprise",
|
||||
"gorush": "Gorush",
|
||||
"squadcast": "Squadcast",
|
||||
@@ -680,7 +680,7 @@
|
||||
"smseagleGroup": "Nombre(s) de grupo(s) de Guía Telefónica",
|
||||
"Unpin": "Dejar de Fijar",
|
||||
"Prefix Custom Message": "Prefijo personalizado",
|
||||
"markdownSupported": "Sintaxis de Markdown soportada. Si estas usando HTML, evita espacios al principio para prevenir problemas de formato.",
|
||||
"markdownSupported": "Sintaxis de Markdown soportada",
|
||||
"Server Address": "Dirección del Servidor",
|
||||
"Learn More": "Aprende Más",
|
||||
"Pick a RR-Type...": "Seleccione un Tipo RR…",
|
||||
@@ -846,7 +846,7 @@
|
||||
"toastSuccessTimeout": "Tiempo de espera para notificaciones de éxito",
|
||||
"toastErrorTimeout": "Tiempo de espera para notificaciones de error",
|
||||
"setupDatabaseChooseDatabase": "¿Qué base de datos te gustaría usar?",
|
||||
"setupDatabaseEmbeddedMariaDB": "No necesitas configurar nada. Esta imagen de Docker tiene incorporado y configurado MariaDB para ti automáticamente. Uptime Kuma se conectará a esta base de datos a través de un socket Unix.",
|
||||
"setupDatabaseEmbeddedMariaDB": "No necesitas configurar nada. Esta imagen de Docker tiene incorporada y configurada MariaDB automáticamente para ti. Uptime Kuma se conectará a esta base de datos a través de un socket Unix.",
|
||||
"setupDatabaseMariaDB": "Conectarse a una base de datos MariaDB externa. Debe configurar la información de conexión a la base de datos.",
|
||||
"setupDatabaseSQLite": "Un archivo de base de datos simple, recomendado para despliegues a pequeña escala. Antes de la versión 2.0.0, Uptime Kuma utilizaba SQLite como base de datos predeterminada.",
|
||||
"dbName": "Nombre de la Base de Datos",
|
||||
@@ -854,7 +854,7 @@
|
||||
"authIncorrectCreds": "Nombre de usuario o contraseña incorrectos.",
|
||||
"2faEnabled": "2FA habilitado.",
|
||||
"2faDisabled": "2FA deshabilitado.",
|
||||
"liquidIntroduction": "El plantillaje se logra a través del lenguaje de plantillas Liquid. Consulte {0} para obtener instrucciones de uso.",
|
||||
"liquidIntroduction": "La plantilla se logra a través del lenguaje de plantillas Liquid. Consulte {0} para obtener instrucciones de uso. Estas son las variables disponibles:",
|
||||
"templateLimitedToUpDownCertNotifications": "solo disponible para notificaciones FUNCIONAL/CAÍDO/Caducidad de certificado",
|
||||
"emailTemplateMsg": "mensaje de la notificación",
|
||||
"emailTemplateLimitedToUpDownNotification": "sólo disponible para latidos FUNCIONAL/CAÍDO, de lo contrario nulo",
|
||||
@@ -939,7 +939,7 @@
|
||||
"threemaSenderIdentity": "ID de Gateway",
|
||||
"threemaSenderIdentityFormat": "8 caracteres, generalmente comienza con *",
|
||||
"Host URL": "URL del anfitrión",
|
||||
"Either enter the hostname of the server you want to connect to or localhost if you intend to use a locally configured mail transfer agent": "Ingresa el nombre del host del servidor al que deseas conectarte o {localhost} si deseas usar un {local_mta}",
|
||||
"Either enter the hostname of the server you want to connect to or localhost if you intend to use a locally configured mail transfer agent": "Ingresa el nombre del host del servidor al que deseas conectarte, o {localhost} si deseas usar un {local_mta}",
|
||||
"smspartnerPhoneNumberHelptext": "El número debe estar en el formato internacional {0}, {1}. Múltiples números deben estar separados por {2}",
|
||||
"smspartnerSenderName": "Nombre del emisor del SMS",
|
||||
"smspartnerApiurl": "Puedes encontrar tu clave API en tu panel de control en {0}",
|
||||
@@ -1213,19 +1213,19 @@
|
||||
"Number of retry attempts if webhook fails": "Número de intentos de reintento (cada 60–180 segundos) si el webhook falla.",
|
||||
"Maximum Retries": "Máximo de reintentos",
|
||||
"sipsakPingWarning": "Para poder utilizar el monitor de SIP Options Ping, necesitas instalar Uptime Kuma sin Docker e instalar el cliente Sipsak en tu servidor.",
|
||||
"Plausible": "Admisible",
|
||||
"Plausible": "Plausible",
|
||||
"Select All": "Seleccionar todo",
|
||||
"Deselect All": "Desmarcar todo",
|
||||
"HTTP Method": "Método HTTP",
|
||||
"webhookPostMethodDesc": "POST es adecuado para la mayoría de servidores HTTP.",
|
||||
"webhookPostMethodDesc": "POST es aceptado para la mayoría de servidores HTTP.",
|
||||
"resendApiKey": "Reenviar la llave API",
|
||||
"deleteGroupMsg": "¿Está seguro que quiere eliminar este grupo?",
|
||||
"deleteGroupMsg": "¿Estás seguro de querer eliminar este grupo?",
|
||||
"settingsDomainExpiry": "Expiración de dominio",
|
||||
"labelDomainExpiry": "Exp. de dominio",
|
||||
"message": "mensaje",
|
||||
"domainExpiryDescription": "Lanzar notificación cuando el nombre de dominio expire en:",
|
||||
"year": "año | años",
|
||||
"descriptionHelpText": "Mostrar en el panel de control interno. Se permite markdown limpio (conserva el espacio y la sangría) antes de mostrarse.",
|
||||
"descriptionHelpText": "Se muestra en el panel principal interno. El código Markdown está permitido y se senea (preserva los espacios y las identaciones) antes de mostrarse.",
|
||||
"json_value": "Valor JSON",
|
||||
"Press Enter to add node": "Pulsa Enter para añadir el nodo",
|
||||
"resendApiHelp": "Crear una llave de API aquí {0}",
|
||||
@@ -1235,7 +1235,7 @@
|
||||
"wsCodeDescription": "Para más información acerca de los códigos de estado, por favor consulta {rfc6455}",
|
||||
"Subprotocol(s)": "Subprotocolo(s)",
|
||||
"certHostnameMismatch": "El nombre de host del certificado no coincide con la URL del monitor.",
|
||||
"twilioMessagingServiceSID": "SID del servicio de Mensajería (opcional)",
|
||||
"twilioMessagingServiceSID": "Servicio de mensajería SID (opcional)",
|
||||
"resendFromEmail": "Correo electrónico del remitente",
|
||||
"resendLeaveBlankForDefaultSubject": "Deja en blanco para utilizar el asunto por defecto",
|
||||
"ignoreSecWebsocketAcceptHeaderDescription": "En caso de que la actualización del websocket sea satisfactoria, permite al servidor no responder con la cabecera Sec-WebSocket-Accept.",
|
||||
@@ -1261,7 +1261,7 @@
|
||||
"systemServiceDescriptionWindows": "Comprueba si el gestor de servicios {service_name} de Windows está ejecutándose",
|
||||
"invalidURL": "URL no válida",
|
||||
"Clone Maintenance": "Clonar Mantenimiento",
|
||||
"ariaPauseMaintenance": "Pausar este cronograma de mantenimiento",
|
||||
"ariaPauseMaintenance": "Pausar este horario de mantenimiento",
|
||||
"systemServiceName": "Nombre del servicio",
|
||||
"systemService": "Servicio del sistema",
|
||||
"systemServiceCommandHint": "Comando utilizado: {command}",
|
||||
@@ -1276,15 +1276,15 @@
|
||||
"Browser not supported": "Navegador no permitido",
|
||||
"labelDomainNameExpiryNotification": "Notificación de expiración de dominio",
|
||||
"Duration (Minutes)": "Duración (Minutos)",
|
||||
"ariaResumeMaintenance": "Reanudar este cronograma de mantenimiento",
|
||||
"ariaCloneMaintenance": "Crear una copia de este cronograma de mantenimiento",
|
||||
"ariaEditMaintenance": "Editar este cronograma de mantenimiento",
|
||||
"ariaDeleteMaintenance": "Eliminar este cronograma de mantenimiento",
|
||||
"ariaResumeMaintenance": "Reanudar este horario de mantenimiento",
|
||||
"ariaCloneMaintenance": "Crear una copia de este horario de mantenimiento",
|
||||
"ariaEditMaintenance": "Editar este horario de mantenimiento",
|
||||
"ariaDeleteMaintenance": "Eliminar este horario de mantenimiento",
|
||||
"SMTP Security": "Seguridad SMTP",
|
||||
"Ignore STARTTLS": "Ignorar STARTTLS",
|
||||
"Use STARTTLS": "Utilizar STARTTLS",
|
||||
"twilloMessagingServiceSIDHelptext": "Ingrese el SID del Servicio de Mensajería aquí si está usando {twillo_messaging_service_help_link} para administrar los remitentes y características",
|
||||
"webhookGetMethodDesc": "GET envía los datos como parámetros de consulta y no permite la configuración del cuerpo de la consulta. Útil para disparar monitores PUSH de Uptime Kuma.",
|
||||
"twilloMessagingServiceSIDHelptext": "Introduce el SID de tu servicio de mensajería si utilizas {twillo_messaging_service_help_link} para genstionar los remitentes y las características",
|
||||
"webhookGetMethodDesc": "GET envía los datos como parámetros de la búsqueda y no permite configurar un cuerpo de mensaje. Útil para disparar los monitores Push de Uptime Kuma.",
|
||||
"showOnlyLastHeartbeat": "Mostrar solo el último latido",
|
||||
"Analytics Type": "Tipo de analíticas",
|
||||
"Google": "Google",
|
||||
@@ -1298,7 +1298,7 @@
|
||||
"checkPriceAt": "Comprueba los precios de {service} en {url}",
|
||||
"noMonitorsOrStatusPagesSelectedError": "No se puede crear un mantenimiento sin monitores afectados o páginas de estado",
|
||||
"noMonitorsSelectedWarning": "Estás creando un mantenimiento sin ningún monitor afectado. ¿Estás seguro de que deseas continuar?",
|
||||
"deleteChildrenMonitors": "Tambien elimina los monitores hijos directos y sus descendientes si los tuvieran | Tambien elimina todos los {count} monitores hijos directos y sus descendientes si los tuvieran",
|
||||
"deleteChildrenMonitors": "Borra también los sub-monitores y sus descendientes si los tuvieran|Borra también todos los {count} sub-monitores directos y sus descendientes si los tuvieran",
|
||||
"OptionalParameters": "Parámetros Opcionales",
|
||||
"aliyun-template-requirements-and-parameters": "La plantilla de SMS de aliyun debe de contener los siguientes parámetros: {parameters}",
|
||||
"aliyun-template-optional-parameters": "Parámetros opcionales: {parameters}",
|
||||
@@ -1310,7 +1310,7 @@
|
||||
"enableSSL": "Habilitar SSL/TLS",
|
||||
"mariadbCaCertificateLabel": "Certificado CA",
|
||||
"unknownDays": "Días desconocidos",
|
||||
"No incidents recorded": "No se registraron incidentes",
|
||||
"No incidents recorded": "No se registraron incidentes.",
|
||||
"Load More": "Cargar más",
|
||||
"mariadbUseSSLHelptext": "Habilita el uso de una conexión cifrada a tu base de datos. Requerido para la mayoría de las bases de datos en la nube.",
|
||||
"mariadbCaCertificateHelptext": "Pegue el certificado CA en formato PEM para utilizarlo con certificados autofirmados. Déjelo en blanco si su base de datos utiliza un certificado firmado por una CA pública.",
|
||||
@@ -1329,48 +1329,5 @@
|
||||
"Only retry if status code check fails": "Reintentar solo si la comprobación del código de estado falla",
|
||||
"retryOnlyOnStatusCodeFailureDescription": "Si está habilitado, los reintentos solo se realizarán cuando falle la comprobación del código de estado HTTP (por ejemplo, si el servidor está caído). Si la comprobación del código de estado es correcta pero falla la consulta JSON, el monitor se marcará como inactivo inmediatamente, sin reintentos.",
|
||||
"responseMaxLengthDescription": "Tamaño máximo de los datos de la respuesta que se van a almacenar. Establezca 0 para ilimitado. Las respuestas más grandes se truncarán. Valor por defecto: 1024 (1 KB)",
|
||||
"logoutCurrentUser": "Cerrar sesión de {username}",
|
||||
"Incident description": "Descripción del incidente",
|
||||
"twilioApiKeyHelptext": "La llave de la API es opcional pero recomendada. Puede proporcionar el SID de la cuenta y el Token de Autorizacion desde la consola de Twilio o el SID de la cuenta y la llave de la API junto con el secreto de la llave de la API",
|
||||
"monitorTypeGameServer": "Servidor de Juego",
|
||||
"monitorTypeDatabase": "Tipo de Monitor de Base de Datos",
|
||||
"monitorTypeSpecial": "Especial",
|
||||
"Recipient Numbers": "Números de destinatarios",
|
||||
"Incident not found or access denied": "No se encontró incidente o acceso denegado",
|
||||
"Past Incidents": "Incidentes pasados",
|
||||
"Incident title": "Título del incidente",
|
||||
"example": "Ejemplo",
|
||||
"Result": "Resultado",
|
||||
"lastUpdatedAt": "Última actualización: {date}",
|
||||
"Actions": "Acciones",
|
||||
"selectAllMonitorsAria": "Seleccionar todos los monitores",
|
||||
"deselectAllMonitorsAria": "Deseleccionar todos los monitores",
|
||||
"lastUpdatedAtFromNow": "Última actualización: {date} ({fromNow})",
|
||||
"See Jira Cloud Docs": "Ver la documentación de Jura Cloud",
|
||||
"Cloud ID": "ID de nube",
|
||||
"API Token": "Token de API",
|
||||
"templateAvailableVariables": "Variables disponibles",
|
||||
"selectMonitorMsg": "Selecciona monitores para realizar acciones",
|
||||
"Examples:": "Ejemplos: {0}",
|
||||
"Pinned incidents are shown prominently on the status page": "Incidentes marcados se muestran prominentemente en la pagina de estado",
|
||||
"Edit Incident": "Editar incidente",
|
||||
"Please input title": "Por favor ingresa título",
|
||||
"Resolve": "Resolver",
|
||||
"Resolved": "Resuelto",
|
||||
"createdAt": "Creado: {date}",
|
||||
"deleteIncidentMsg": "Estas seguro que quieres eliminar este incidente?",
|
||||
"Certificate Chain:": "Cadena de certificado:",
|
||||
"Please input content": "Por favor ingresa contenido",
|
||||
"dateCreatedAtFromNow": "Fecha de creación: {date} ({fromNow})",
|
||||
"360messengerAuthToken": "Clave de la API 360messenger",
|
||||
"360messengerGroupId": "ID de grupo 360messenger",
|
||||
"360messengerGroupList": "Grupos de WhatsApp",
|
||||
"ntfyUseTemplateDescription": "Habilite esta opción para personalizar los títulos y mensajes de notificación mediante plantillas LiquidJS",
|
||||
"ntfyCustomTitle": "Plantilla de título personalizado",
|
||||
"ntfyCustomMessage": "Plantilla de mensaje personalizado",
|
||||
"ntfyNotificationTemplateFallback": "Dejar en blanco para utilizar el formato predeterminado de Uptime Kuma",
|
||||
"Screenshot Delay": "Retraso de captura de pantalla (espera {milliseconds})",
|
||||
"milliseconds": "{n} milisegundos | {n} milisegundos",
|
||||
"snmpV3Username": "Nombre de usuario SNMPv3",
|
||||
"ntfyUseTemplate": "Personalizar plantillas de notificación"
|
||||
"logoutCurrentUser": "Cerrar sesión de {username}"
|
||||
}
|
||||
|
||||
@@ -1611,14 +1611,5 @@
|
||||
"360messengerWayToWriteRecipient": "Saisissez un ou plusieurs numéros de téléphone au format international sans signe plus (par exemple : {0}). Séparez les numéros par des virgules.",
|
||||
"monitorTypeSpecial": "Spécial",
|
||||
"monitorTypeGameServer": "Serveur de jeu",
|
||||
"monitorTypeDatabase": "Sonde de Type base de données",
|
||||
"fluxerMessageFormatNormal": "Normal (intégrations riches)",
|
||||
"fluxerUseMessageTemplate": "Utiliser un modèle de message personnalisé",
|
||||
"Fluxer Webhook URL": "URL du Webhook de Fluxer",
|
||||
"wayToGetFluxerURL": "Vous pouvez obtenir cela en allant dans les paramètres du canal cible > Webhooks > Créer un webhook > Copier l’URL du webhook.",
|
||||
"fluxerMessageFormat": "Format du message",
|
||||
"fluxerMessageFormatCustom": "Modèle personnalisé",
|
||||
"fluxerMessageTemplate": "Modèle de message",
|
||||
"fluxerMessageFormatMinimalist": "Minimalist (statut court",
|
||||
"fluxerUseMessageTemplateDescription": "Si activé, le message sera envoyé en utilisant un modèle personnalisé (LiquidJS). Laissez vide pour utiliser le format Uptime Kuma par défaut."
|
||||
"monitorTypeDatabase": "Sonde de Type base de données"
|
||||
}
|
||||
|
||||
@@ -1377,7 +1377,7 @@
|
||||
"TLS Alert Spec": "RFC 8446",
|
||||
"Suppress Notifications": "Fógraí a Chosc",
|
||||
"discordSuppressNotificationsHelptext": "Nuair a bheidh sé cumasaithe, cuirfear teachtaireachtaí chuig an gcainéal ach ní spreagfar fógraí brú ná fógraí deisce do fhaighteoirí.",
|
||||
"domain_expiry_unsupported_is_icann": "Ní iarrthóir é an fearann \"{domain}\" le haghaidh monatóireachta ar dhul in éag fearainn, toisc nach bhfuil a iarmhír phoiblí \".{publicSuffix}\" á bhainistiú ag ICANN",
|
||||
"domain_expiry_unsupported_is_icann": "Ní iarrthóir é an fearann \"{domain}\" le haghaidh monatóireachta ar dhul in éag fearainn, toisc nach bhfuil a iarmhír phoiblí \".{publicSuffix}\" ICAN",
|
||||
"notificationUniversal": "Uilíoch",
|
||||
"notificationChatPlatforms": "Ardáin Comhrá",
|
||||
"notificationPushServices": "Seirbhísí Brúigh",
|
||||
@@ -1540,30 +1540,5 @@
|
||||
"GlobalpingIpFamilyInfo": "An leagan IP le húsáid. Ní cheadaítear é seo ach amháin má tá an sprioc ina hainm óstach.",
|
||||
"GlobalpingResolverInfo": "Seoladh IPv4/IPv6 nó Ainm Fearainn Cáilithe go Lán (FQDN). Is é an réamhshocrú ná réiteoir líonra áitiúil an tóireadóra. Is féidir leat an freastalaí réiteora a athrú am ar bith.",
|
||||
"Jira Service Management": "Bainistíocht Seirbhíse Jira",
|
||||
"Google Apps Script Webhook URL": "URL Gréasáin-chrúca Script Google Apps",
|
||||
"360messengerEnableSendToGroup": "Cumasaigh seoltaí chuig grúpa(í) WhatsApp",
|
||||
"360messengerAuthToken": "Eochair API 360messenger",
|
||||
"360messengerRecipient": "Uimhir(í) theileafóin an fhaighteora",
|
||||
"360messengerGroupId": "Aitheantas Grúpa 360messenger",
|
||||
"360messengerUseTemplate": "Úsáid teimpléad teachtaireachta saincheaptha",
|
||||
"360messengerTemplate": "Teimpléad Teachtaireachta 360messenger",
|
||||
"360messengerGroupList": "Grúpaí WhatsApp",
|
||||
"360messengerSelectGroupList": "Roghnaigh grúpa le cur leis",
|
||||
"360messengerSelectedGroupID": "Aitheantais Ghrúpa Roghnaithe",
|
||||
"360messengerCustomMessageTemplate": "Teimpléad teachtaireachta saincheaptha",
|
||||
"360messengerEnableCustomMessage": "Cumasaigh teimpléad teachtaireachta saincheaptha in ionad an teachtaireachta réamhshocraithe.",
|
||||
"360messengerMessageTemplate": "Teimpléad teachtaireachta",
|
||||
"360messengerWayToGetUrlAndToken": "Is féidir leat d'eochair API 360messenger a fháil ó {0}.",
|
||||
"360messengerErrorNoApiKey": "Cuir isteach d’eochair API 360messenger ar dtús.",
|
||||
"360messengerErrorNoGroups": "Ní bhfuarthas aon ghrúpaí WhatsApp don chuntas seo.",
|
||||
"360messengerErrorApi": "Ní féidir liosta na ngrúpaí WhatsApp a luchtú (Earráid {statusCode}: {message}).",
|
||||
"360messengerErrorGeneric": "Ní féidir an liosta grúpa WhatsApp a luchtú: {message}",
|
||||
"GlobalpingLocationDescription": "Glacann an réimse suímh le hilchríocha, tíortha, réigiúin, cathracha, ASNanna, ISPanna, nó réigiúin scamall. Is féidir leat scagairí a chomhcheangal le {plus} (m.sh. {amazonPlusGermany} nó {comcastPlusCalifornia}). Más méadracht thábhachtach í an mhoill, bain úsáid as scagairí chun an suíomh a chúngú síos go réigiún beag chun spící a sheachaint agus socraigh an scagaire {datacenter} ar mhaithe le cobhsaíocht níos fearr. {fullDocs}.",
|
||||
"GlobalpingMultipleLocationsError": "Ní thacaítear le hilshuíomhanna, bain úsáid as suíomh amháin do gach monatóir le do thoil.",
|
||||
"360messengerWayToWriteRecipient": "Cuir isteach uimhir theileafóin amháin nó níos mó i bhformáid idirnáisiúnta gan móide tosaigh (m.sh. {0}). Scar uimhreacha iolracha le camóga.",
|
||||
"signalUseTemplate": "Úsáid teimpléad teachtaireachta saincheaptha",
|
||||
"signalUseTemplateDescription": "Má tá sé cumasaithe, seolfar an teachtaireacht ag baint úsáide as teimpléad saincheaptha. Is féidir leat teimpléadú Liquid a úsáid chun formáid an fhógra a shaincheapadh.",
|
||||
"monitorTypeGameServer": "Freastalaí Cluiche",
|
||||
"monitorTypeDatabase": "Cineál Monatóra Bunachar Sonraí",
|
||||
"monitorTypeSpecial": "Speisialta"
|
||||
"Google Apps Script Webhook URL": "URL Gréasáin-chrúca Script Google Apps"
|
||||
}
|
||||
|
||||
@@ -1275,8 +1275,5 @@
|
||||
"Ignore STARTTLS": "Ignora STARTTLS",
|
||||
"Use STARTTLS": "Utilizza STARTTLS",
|
||||
"Enter the list of nodes": "Inserisci l'elenco dei nodi di gestione RabbitMQ",
|
||||
"Press Enter to add node": "Premi Invio per aggiungere il nodo",
|
||||
"enableSSL": "Abilita SSL/TLS",
|
||||
"mariadbUseSSLHelptext": "Abilita per usare una connessione criptata per il tuo database. Richiesto dalla maggior parte dei database cloud.",
|
||||
"mariadbCaCertificateLabel": "Certificato CA"
|
||||
"Press Enter to add node": "Premi Invio per aggiungere il nodo"
|
||||
}
|
||||
|
||||
@@ -3,72 +3,5 @@
|
||||
"setupDatabaseChooseDatabase": "Kuru datubāzi izmantosiet?",
|
||||
"setupDatabaseEmbeddedMariaDB": "Jums nav nekas jādara. Docker imidžā ir iebūvēta un automātiski konfigurēta MariaDB datubāze. Uptime Kuma pieslēgsies šai datubāzei izmantojot unix soketu.",
|
||||
"setupDatabaseSQLite": "Vienkāršs datu bāzes fails, iesakāms maza izmēra risinājumiem. Pirms versijas v2.0.0 SQLite bija noklusējuma datubāze.",
|
||||
"setupDatabaseMariaDB": "Pieslēgties ārējai MariaDB datubāzei. Jums būs jākonfigurē datubāzes pieslēgšanās informācija.",
|
||||
"Name": "Nosaukums",
|
||||
"Ping": "Ping",
|
||||
"Dashboard": "Panelis",
|
||||
"dbName": "Datubāzes nosaukums",
|
||||
"enableSSL": "Iespējot SSL/TLS",
|
||||
"Settings": "Iestatījumi",
|
||||
"Help": "Palīdzība",
|
||||
"New Update": "Jauns atjauninājums",
|
||||
"Language": "Valoda",
|
||||
"Appearance": "Izskats",
|
||||
"Theme": "Tēma",
|
||||
"General": "Vispārīgi",
|
||||
"Game": "Spēle",
|
||||
"mariadbCaCertificateLabel": "CA sertifikāts",
|
||||
"Primary Base URL": "Galvenais bāzes URL",
|
||||
"Check Update On GitHub": "Pārbaudīt atjauninājumu GitHub",
|
||||
"List": "Saraksts",
|
||||
"Home": "Sākums",
|
||||
"Add": "Pievienot",
|
||||
"Add New Monitor": "Pievienot jaunu monitoru",
|
||||
"Quick Stats": "Ātrā statistika",
|
||||
"Down": "Nedarbojas",
|
||||
"Pending": "Rindā",
|
||||
"statusMaintenance": "Tehniskā apkope",
|
||||
"Maintenance": "Tehniskā apkope",
|
||||
"Unknown": "Nezināms",
|
||||
"unknownDays": "Nezināms dienu skaits",
|
||||
"Cannot connect to the socket server": "Nevar izveidot savienojumu ar soketa serveri",
|
||||
"Reconnecting...": "Savienojas...",
|
||||
"General Monitor Type": "Vispārīgais monitora veids",
|
||||
"pauseDashboardHome": "Pauze",
|
||||
"Pause": "Pauze",
|
||||
"DateTime": "Datums un laiks",
|
||||
"Specific Monitor Type": "Specifiskais monitora veids",
|
||||
"settingUpDatabaseMSG": "Tiek uzstādīta datubāze. Uzgaidiet, lūdzu, tas var prasīt laiku.",
|
||||
"mariadbUseSSLHelptext": "Iespējot šifrētu savienojumu ar jūsu datubāzi. Nepieciešams lielākajai daļai mākoņdatubāžu.",
|
||||
"mariadbCaCertificateHelptext": "Ielīmējiet CA sertifikātu PEM formātā, lai to izmantotu ar pašparakstītiem sertifikātiem. Atstājiet tukšu, ja jūsu datubāze izmanto publiskas sertifikācijas iestādes parakstītu sertifikātu.",
|
||||
"Passive Monitor Type": "Pasīvais monitora veids",
|
||||
"markdownSupported": "Tiek atbalstīta Markdown sintakse. Ja izmantojat HTML, izvairieties no atstarpēm rindas sākumā, lai novērstu formatēšanas problēmas.",
|
||||
"versionIs": "Versija: {version}",
|
||||
"monitorTypeGameServer": "Spēļu serveris",
|
||||
"monitorTypeDatabase": "Datubāzes monitora veids",
|
||||
"monitorTypeSpecial": "Specifisks",
|
||||
"Message": "Ziņa",
|
||||
"No incidents recorded": "Nav reģistrētu incidentu",
|
||||
"Load More": "Ielādēt vairāk",
|
||||
"Loading...": "Ielāde...",
|
||||
"No important events": "Nav svarīgu notikumu",
|
||||
"Resume": "Turpināt",
|
||||
"Edit": "Labot",
|
||||
"Delete": "Dzēst",
|
||||
"Current": "Pašreizējais",
|
||||
"Uptime": "Darbības laiks",
|
||||
"Cert Exp.": "Sert. term.",
|
||||
"Monitors": "{n} monitors | {n} monitori",
|
||||
"now": "tagad",
|
||||
"time ago": "pirms {0}",
|
||||
"days": "{n} diena | {n} dienas",
|
||||
"hours": "{n} stunda | {n} stundas",
|
||||
"minutes": "{n} minūte | {n} minūtes",
|
||||
"minuteShort": "{n} minūte | {n} minūtes",
|
||||
"years": "{n} gads| {n} gadi",
|
||||
"Response": "Atbilde",
|
||||
"Pin this incident": "Piespraust šo incidentu",
|
||||
"Monitor Type": "Monitora tips",
|
||||
"Up": "Darbojas",
|
||||
"Status": "Status"
|
||||
"setupDatabaseMariaDB": "Pieslēgties ārējai MariaDB datubāzei. Jums būs jākonfigurē datubāzes pieslēgšanās informācija."
|
||||
}
|
||||
|
||||
@@ -994,7 +994,7 @@
|
||||
"and": "en",
|
||||
"snmpCommunityStringHelptext": "Deze string fungeert als een wachtwoord om toegang tot SNMP-apparaten te verifiëren en te beheren. Match het met de configuratie van uw SNMP-apparaat.",
|
||||
"groupOnesenderDesc": "Zorg ervoor dat de GroupID juist is. Om een bericht naar een groep te sturen, bijvoorbeeld: 628123456789-342345",
|
||||
"privateOnesenderDesc": "Zorg ervoor dat het telefoonnummer juist is. Om een bericht te sturen naar een privénummer, bijvoorbeeld: 628123456789",
|
||||
"privateOnesenderDesc": "Zorg ervoor dat het telefoonnummer juist is. Om een bericht te sturen naar een privenummer, bijvoorbeeld: 628123456789",
|
||||
"now": "nu",
|
||||
"time ago": "{0} geleden",
|
||||
"-year": "-jaar",
|
||||
@@ -1007,7 +1007,7 @@
|
||||
"Host Onesender": "Host Onesender",
|
||||
"Token Onesender": "Token Onesender",
|
||||
"Recipient Type": "Ontvanger Type",
|
||||
"Private Number": "Privénummer",
|
||||
"Private Number": "Privenummer",
|
||||
"Group ID": "Groep ID",
|
||||
"wayToGetOnesenderUrlandToken": "U kunt de URL en Token krijgen door naar de Onesender website te gaan. Meer informatie {0}",
|
||||
"Add Remote Browser": "Externe browser toevoegen",
|
||||
@@ -1551,27 +1551,5 @@
|
||||
"expectedTlsAlertDescription": "Selecteer de TLS-waarschuwing waarvan u verwacht dat de server deze retourneert. Gebruik {code} om te verifiëren dat mTLS-eindpunten verbindingen weigeren zonder clientcertificaten. Zie {link} voor details.",
|
||||
"Protocol": "Protocol",
|
||||
"domain_expiry_unsupported_missing_target": "Geen geldige domeinnaam of hostnaam is ingesteld voor deze monitor",
|
||||
"Endpoint": "Endpoint",
|
||||
"signalUseTemplate": "Gebruik een persoonlijk berichtensjabloon",
|
||||
"signalUseTemplateDescription": "Indien ingeschakeld, wordt het bericht verzonden met een persoonlijk sjabloon. U kunt Liquid-templating gebruiken om het meldingsformaat aan te passen.",
|
||||
"360messengerAuthToken": "360messenger API Sleutel",
|
||||
"360messengerRecipient": "Telefoonnummer (s) van de ontvanger",
|
||||
"360messengerGroupId": "360messenger Groep ID",
|
||||
"360messengerUseTemplate": "Gebruik een persoonlijk berichtensjabloon",
|
||||
"360messengerTemplate": "360messenger berichtensjabloon",
|
||||
"360messengerGroupList": "WhatsApp groepen",
|
||||
"360messengerSelectGroupList": "Selecteer een groep om toe te voegen",
|
||||
"360messengerSelectedGroupID": "Geselecteerde Groep ID(s)",
|
||||
"360messengerEnableSendToGroup": "Schakel versturen naar WhatsApp groep(en) in",
|
||||
"360messengerCustomMessageTemplate": "Persoonlijk berichtensjabloon",
|
||||
"360messengerEnableCustomMessage": "Schakel een persoonlijk berichtensjabloon in i.p.v. het standaardbericht.",
|
||||
"360messengerMessageTemplate": "Berichtensjabloon",
|
||||
"teamsEnableTags": "Inclusief tags",
|
||||
"teamsEnableTagsDescription": "Indien ingeschakeld zal het bericht de monitortags bevatten.",
|
||||
"certificateExpiryNotificationHelp": "De hoeveelheid dagen op voorhand kan in de instellingen worden geconfigureerd.",
|
||||
"matrixUseTemplate": "Gebruik een persoonlijk berichtensjabloon",
|
||||
"matrixUseTemplateDescription": "Indien ingeschakeld, wordt het bericht verzonden met een persoonlijk sjabloon.",
|
||||
"monitorTypeGameServer": "Spelserver",
|
||||
"monitorTypeDatabase": "Databank Monitor Type",
|
||||
"monitorTypeSpecial": "Speciaal"
|
||||
"Endpoint": "Endpoint"
|
||||
}
|
||||
|
||||
@@ -1578,14 +1578,5 @@
|
||||
"360messengerWayToWriteRecipient": "Insira um ou mais números de telefone no formato internacional, sem o sinal de mais inicial (por exemplo, {0}). Separe vários números com vírgulas.",
|
||||
"360messengerErrorApi": "Não foi possível carregar a lista de grupos do WhatsApp (Erro {statusCode}: {message}).",
|
||||
"GlobalpingMultipleLocationsError": "Não é possível realizar várias localizações; utilize uma única localização para cada monitor.",
|
||||
"GlobalpingLocationDescription": "O campo de localização aceita continentes, países, regiões, cidades, ASNs, ISPs ou regiões de nuvem. Você pode combinar filtros com {plus} (por exemplo, {amazonPlusGermany} ou {comcastPlusCalifornia}). Se a latência for uma métrica importante, use filtros para restringir a localização a uma região pequena para evitar picos e, para maior estabilidade, defina o filtro {datacenter}. {fullDocs}.",
|
||||
"fluxerMessageFormat": "Formato da mensagem",
|
||||
"fluxerMessageFormatNormal": "Normal (rich embeds)",
|
||||
"fluxerMessageFormatMinimalist": "Minimalista (status curto)",
|
||||
"fluxerUseMessageTemplate": "Use um modelo de mensagem personalizado",
|
||||
"fluxerMessageTemplate": "Modelo de mensagem",
|
||||
"fluxerMessageFormatCustom": "Modelo personalizado",
|
||||
"fluxerUseMessageTemplateDescription": "Se ativada, a mensagem será enviada usando um modelo personalizado (LiquidJS). Deixe em branco para usar o formato padrão do Uptime Kuma.",
|
||||
"Fluxer Webhook URL": "URL do Webhook do Fluxer",
|
||||
"wayToGetFluxerURL": "Você pode obter essa informação acessando as configurações do canal de destino > Webhooks > Criar Webhook > Copiar URL do Webhook."
|
||||
"GlobalpingLocationDescription": "O campo de localização aceita continentes, países, regiões, cidades, ASNs, ISPs ou regiões de nuvem. Você pode combinar filtros com {plus} (por exemplo, {amazonPlusGermany} ou {comcastPlusCalifornia}). Se a latência for uma métrica importante, use filtros para restringir a localização a uma região pequena para evitar picos e, para maior estabilidade, defina o filtro {datacenter}. {fullDocs}."
|
||||
}
|
||||
|
||||
@@ -1431,7 +1431,7 @@
|
||||
"legacyOctopushEndpoint": "Staršia verzia Octopush-DM (koncový bod: {url})",
|
||||
"Suppress Notifications": "Stlmiť oznámenia",
|
||||
"discordSuppressNotificationsHelptext": "Ak je táto funkcia povolená, správy budú odosielané do kanála, ale nebudú spúšťať push alebo desktopové notifikácie pre príjemcov.",
|
||||
"domain_expiry_unsupported_is_icann": "Doména „{domain}“ nie je kandidátom na monitorovanie vypršania platnosti domény, pretože jej verejná prípona „.{publicSuffix}“ nie je spravovaná organizáciou ICANN",
|
||||
"domain_expiry_unsupported_is_icann": "Doména „{domain}“ nie je kandidátom na monitorovanie vypršania platnosti domény, pretože jej verejná prípona „.{publicSuffix}“ nie je ICAN",
|
||||
"snmpV3Username": "Používateľské meno SNMPv3",
|
||||
"WeCom Mentioned Mobile List Description": "Zadajte telefónne čísla, ktoré chcete označiť. Viac čísel oddeľte čiarkami. Použite {'@'}all, aby ste označili všetkých.",
|
||||
"WeCom Mentioned Mobile List": "WeCom zoznam zmienených",
|
||||
@@ -1546,28 +1546,5 @@
|
||||
"certificateExpiryNotificationHelp": "Počet dní vopred je možné nastaviť v nastaveniach.",
|
||||
"signalUseTemplate": "Použite vlastnú šablónu správy",
|
||||
"signalUseTemplateDescription": "Ak je táto funkcia povolená, správa bude odoslaná pomocou vlastnej šablóny. Na prispôsobenie formátu oznámenia môžete použiť šablóny Liquid.",
|
||||
"domainExpiryNotificationHelp": "Počet dní vopred je možné nastaviť v nastaveniach.",
|
||||
"monitorTypeDatabase": "Typ monitoru databázy",
|
||||
"monitorTypeGameServer": "Herný server",
|
||||
"monitorTypeSpecial": "Špeciálny",
|
||||
"360messengerAuthToken": "API kľúč 360messenger",
|
||||
"360messengerRecipient": "Telefónne číslo/a príjemcu",
|
||||
"360messengerGroupId": "360messenger ID skupiny",
|
||||
"360messengerUseTemplate": "Použite vlastnú šablónu správy",
|
||||
"360messengerTemplate": "Šablóna správy 360messenger",
|
||||
"360messengerGroupList": "Skupiny WhatsApp",
|
||||
"360messengerSelectGroupList": "Vyberte skupinu, ktorú chcete pridať",
|
||||
"360messengerSelectedGroupID": "Vybrané ID skupiny/ín",
|
||||
"360messengerEnableSendToGroup": "Povoliť odosielanie do skupín WhatsApp",
|
||||
"360messengerCustomMessageTemplate": "Šablóna vlastnej správy",
|
||||
"360messengerEnableCustomMessage": "Povoliť vlastnú šablónu správy namiesto predvolenej správy.",
|
||||
"360messengerMessageTemplate": "Šablóna správy",
|
||||
"360messengerWayToWriteRecipient": "Zadajte jedno alebo viacero telefónnych čísel v medzinárodnom formáte bez predpony plus (napr. {0}). Viacero čísel oddeľte čiarkami.",
|
||||
"360messengerErrorNoApiKey": "Najskôr zadajte svoj API kľúč 360messenger.",
|
||||
"360messengerErrorApi": "Nie je možné načítať zoznam skupín WhatsApp (Chyba {statusCode}: {message}).",
|
||||
"360messengerErrorGeneric": "Nie je možné načítať zoznam skupín WhatsApp: {message}",
|
||||
"GlobalpingMultipleLocationsError": "Viacnásobné polohy nie sú podporované, pre každý monitor použite jednu polohu.",
|
||||
"360messengerWayToGetUrlAndToken": "API kľúč pre 360messenger môžete získať na {0}.",
|
||||
"360messengerErrorNoGroups": "Pre tento účet neboli nájdené žiadne skupiny WhatsApp.",
|
||||
"GlobalpingLocationDescription": "Do poľa polohy môžete zadávať kontinenty, krajiny, regióny, mestá, ASN, ISP alebo cloudové regióny. Filtre môžete kombinovať pomocou znaku {plus} (napr. {amazonPlusGermany} alebo {comcastPlusCalifornia}). Ak je dôležitým ukazovateľom latencia, použite filtre na zúženie polohy na malý región, aby ste sa vyhli výkyvom, a pre lepšiu stabilitu nastavte filter {datacenter}. {fullDocs}."
|
||||
"domainExpiryNotificationHelp": "Počet dní vopred je možné nastaviť v nastaveniach."
|
||||
}
|
||||
|
||||
493
src/util.js
493
src/util.js
@@ -1,493 +0,0 @@
|
||||
"use strict";
|
||||
/*!
|
||||
// Common Util for frontend and backend
|
||||
//
|
||||
// DOT NOT MODIFY util.js!
|
||||
// Need to run "npm run tsc" to compile if there are any changes.
|
||||
//
|
||||
// Backend uses the compiled file util.js
|
||||
// Frontend uses util.ts
|
||||
*/
|
||||
var _a;
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.CONSOLE_STYLE_FgLightBlue = exports.CONSOLE_STYLE_FgLightGreen = exports.CONSOLE_STYLE_FgOrange = exports.CONSOLE_STYLE_FgGray = exports.CONSOLE_STYLE_FgWhite = exports.CONSOLE_STYLE_FgCyan = exports.CONSOLE_STYLE_FgMagenta = exports.CONSOLE_STYLE_FgBlue = exports.CONSOLE_STYLE_FgYellow = exports.CONSOLE_STYLE_FgGreen = exports.CONSOLE_STYLE_FgRed = exports.CONSOLE_STYLE_FgBlack = exports.CONSOLE_STYLE_Hidden = exports.CONSOLE_STYLE_Reverse = exports.CONSOLE_STYLE_Blink = exports.CONSOLE_STYLE_Underscore = exports.CONSOLE_STYLE_Dim = exports.CONSOLE_STYLE_Bright = exports.CONSOLE_STYLE_Reset = exports.RESPONSE_BODY_LENGTH_MAX = exports.RESPONSE_BODY_LENGTH_DEFAULT = exports.PING_PER_REQUEST_TIMEOUT_DEFAULT = exports.PING_PER_REQUEST_TIMEOUT_MAX = exports.PING_PER_REQUEST_TIMEOUT_MIN = exports.PING_COUNT_DEFAULT = exports.PING_COUNT_MAX = exports.PING_COUNT_MIN = exports.PING_GLOBAL_TIMEOUT_DEFAULT = exports.PING_GLOBAL_TIMEOUT_MAX = exports.PING_GLOBAL_TIMEOUT_MIN = exports.PING_PACKET_SIZE_DEFAULT = exports.PING_PACKET_SIZE_MAX = exports.PING_PACKET_SIZE_MIN = exports.INCIDENT_PAGE_SIZE = exports.MIN_INTERVAL_SECOND = exports.MAX_INTERVAL_SECOND = exports.SQL_DATETIME_FORMAT_WITHOUT_SECOND = exports.SQL_DATETIME_FORMAT = exports.SQL_DATE_FORMAT = exports.STATUS_PAGE_MAINTENANCE = exports.STATUS_PAGE_PARTIAL_DOWN = exports.STATUS_PAGE_ALL_UP = exports.STATUS_PAGE_ALL_DOWN = exports.MAINTENANCE = exports.PENDING = exports.UP = exports.DOWN = exports.appName = exports.isNode = exports.isDev = void 0;
|
||||
exports.TYPES_WITH_DOMAIN_EXPIRY_SUPPORT_VIA_FIELD = exports.evaluateJsonQuery = exports.intHash = exports.localToUTC = exports.utcToLocal = exports.utcToISODateTime = exports.isoToUTCDateTime = exports.parseTimeFromTimeObject = exports.parseTimeObject = exports.getMonitorRelativeURL = exports.genSecret = exports.getCryptoRandomInt = exports.getRandomInt = exports.getRandomArbitrary = exports.TimeLogger = exports.polyfill = exports.log = exports.debug = exports.ucfirst = exports.sleep = exports.flipStatus = exports.badgeConstants = exports.CONSOLE_STYLE_BgGray = exports.CONSOLE_STYLE_BgWhite = exports.CONSOLE_STYLE_BgCyan = exports.CONSOLE_STYLE_BgMagenta = exports.CONSOLE_STYLE_BgBlue = exports.CONSOLE_STYLE_BgYellow = exports.CONSOLE_STYLE_BgGreen = exports.CONSOLE_STYLE_BgRed = exports.CONSOLE_STYLE_BgBlack = exports.CONSOLE_STYLE_FgPink = exports.CONSOLE_STYLE_FgBrown = exports.CONSOLE_STYLE_FgViolet = void 0;
|
||||
const dayjs_1 = require("dayjs");
|
||||
const jsonata = require("jsonata");
|
||||
exports.isDev = process.env.NODE_ENV === "development";
|
||||
exports.isNode = typeof process !== "undefined" && ((_a = process === null || process === void 0 ? void 0 : process.versions) === null || _a === void 0 ? void 0 : _a.node);
|
||||
const dayjs = exports.isNode ? require("dayjs") : dayjs_1.default;
|
||||
exports.appName = "Uptime Kuma";
|
||||
exports.DOWN = 0;
|
||||
exports.UP = 1;
|
||||
exports.PENDING = 2;
|
||||
exports.MAINTENANCE = 3;
|
||||
exports.STATUS_PAGE_ALL_DOWN = 0;
|
||||
exports.STATUS_PAGE_ALL_UP = 1;
|
||||
exports.STATUS_PAGE_PARTIAL_DOWN = 2;
|
||||
exports.STATUS_PAGE_MAINTENANCE = 3;
|
||||
exports.SQL_DATE_FORMAT = "YYYY-MM-DD";
|
||||
exports.SQL_DATETIME_FORMAT = "YYYY-MM-DD HH:mm:ss";
|
||||
exports.SQL_DATETIME_FORMAT_WITHOUT_SECOND = "YYYY-MM-DD HH:mm";
|
||||
exports.MAX_INTERVAL_SECOND = 2073600;
|
||||
exports.MIN_INTERVAL_SECOND = 1;
|
||||
exports.INCIDENT_PAGE_SIZE = 10;
|
||||
exports.PING_PACKET_SIZE_MIN = 1;
|
||||
exports.PING_PACKET_SIZE_MAX = 65500;
|
||||
exports.PING_PACKET_SIZE_DEFAULT = 56;
|
||||
exports.PING_GLOBAL_TIMEOUT_MIN = 1;
|
||||
exports.PING_GLOBAL_TIMEOUT_MAX = 300;
|
||||
exports.PING_GLOBAL_TIMEOUT_DEFAULT = 10;
|
||||
exports.PING_COUNT_MIN = 1;
|
||||
exports.PING_COUNT_MAX = 100;
|
||||
exports.PING_COUNT_DEFAULT = 1;
|
||||
exports.PING_PER_REQUEST_TIMEOUT_MIN = 1;
|
||||
exports.PING_PER_REQUEST_TIMEOUT_MAX = 60;
|
||||
exports.PING_PER_REQUEST_TIMEOUT_DEFAULT = 2;
|
||||
exports.RESPONSE_BODY_LENGTH_DEFAULT = 1024;
|
||||
exports.RESPONSE_BODY_LENGTH_MAX = 1024 * 1024;
|
||||
exports.CONSOLE_STYLE_Reset = "\x1b[0m";
|
||||
exports.CONSOLE_STYLE_Bright = "\x1b[1m";
|
||||
exports.CONSOLE_STYLE_Dim = "\x1b[2m";
|
||||
exports.CONSOLE_STYLE_Underscore = "\x1b[4m";
|
||||
exports.CONSOLE_STYLE_Blink = "\x1b[5m";
|
||||
exports.CONSOLE_STYLE_Reverse = "\x1b[7m";
|
||||
exports.CONSOLE_STYLE_Hidden = "\x1b[8m";
|
||||
exports.CONSOLE_STYLE_FgBlack = "\x1b[30m";
|
||||
exports.CONSOLE_STYLE_FgRed = "\x1b[31m";
|
||||
exports.CONSOLE_STYLE_FgGreen = "\x1b[32m";
|
||||
exports.CONSOLE_STYLE_FgYellow = "\x1b[33m";
|
||||
exports.CONSOLE_STYLE_FgBlue = "\x1b[34m";
|
||||
exports.CONSOLE_STYLE_FgMagenta = "\x1b[35m";
|
||||
exports.CONSOLE_STYLE_FgCyan = "\x1b[36m";
|
||||
exports.CONSOLE_STYLE_FgWhite = "\x1b[37m";
|
||||
exports.CONSOLE_STYLE_FgGray = "\x1b[90m";
|
||||
exports.CONSOLE_STYLE_FgOrange = "\x1b[38;5;208m";
|
||||
exports.CONSOLE_STYLE_FgLightGreen = "\x1b[38;5;119m";
|
||||
exports.CONSOLE_STYLE_FgLightBlue = "\x1b[38;5;117m";
|
||||
exports.CONSOLE_STYLE_FgViolet = "\x1b[38;5;141m";
|
||||
exports.CONSOLE_STYLE_FgBrown = "\x1b[38;5;130m";
|
||||
exports.CONSOLE_STYLE_FgPink = "\x1b[38;5;219m";
|
||||
exports.CONSOLE_STYLE_BgBlack = "\x1b[40m";
|
||||
exports.CONSOLE_STYLE_BgRed = "\x1b[41m";
|
||||
exports.CONSOLE_STYLE_BgGreen = "\x1b[42m";
|
||||
exports.CONSOLE_STYLE_BgYellow = "\x1b[43m";
|
||||
exports.CONSOLE_STYLE_BgBlue = "\x1b[44m";
|
||||
exports.CONSOLE_STYLE_BgMagenta = "\x1b[45m";
|
||||
exports.CONSOLE_STYLE_BgCyan = "\x1b[46m";
|
||||
exports.CONSOLE_STYLE_BgWhite = "\x1b[47m";
|
||||
exports.CONSOLE_STYLE_BgGray = "\x1b[100m";
|
||||
const consoleModuleColors = [
|
||||
exports.CONSOLE_STYLE_FgCyan,
|
||||
exports.CONSOLE_STYLE_FgGreen,
|
||||
exports.CONSOLE_STYLE_FgLightGreen,
|
||||
exports.CONSOLE_STYLE_FgBlue,
|
||||
exports.CONSOLE_STYLE_FgLightBlue,
|
||||
exports.CONSOLE_STYLE_FgMagenta,
|
||||
exports.CONSOLE_STYLE_FgOrange,
|
||||
exports.CONSOLE_STYLE_FgViolet,
|
||||
exports.CONSOLE_STYLE_FgBrown,
|
||||
exports.CONSOLE_STYLE_FgPink,
|
||||
];
|
||||
const consoleLevelColors = {
|
||||
INFO: exports.CONSOLE_STYLE_FgCyan,
|
||||
WARN: exports.CONSOLE_STYLE_FgYellow,
|
||||
ERROR: exports.CONSOLE_STYLE_FgRed,
|
||||
DEBUG: exports.CONSOLE_STYLE_FgGray,
|
||||
};
|
||||
exports.badgeConstants = {
|
||||
naColor: "#999",
|
||||
defaultUpColor: "#66c20a",
|
||||
defaultWarnColor: "#eed202",
|
||||
defaultDownColor: "#c2290a",
|
||||
defaultPendingColor: "#f8a306",
|
||||
defaultMaintenanceColor: "#1747f5",
|
||||
defaultPingColor: "blue",
|
||||
defaultStyle: "flat",
|
||||
defaultPingValueSuffix: "ms",
|
||||
defaultPingLabelSuffix: "h",
|
||||
defaultUptimeValueSuffix: "%",
|
||||
defaultUptimeLabelSuffix: "h",
|
||||
defaultCertExpValueSuffix: " days",
|
||||
defaultCertExpLabelSuffix: "h",
|
||||
defaultCertExpireWarnDays: "14",
|
||||
defaultCertExpireDownDays: "7",
|
||||
};
|
||||
function flipStatus(s) {
|
||||
if (s === exports.UP) {
|
||||
return exports.DOWN;
|
||||
}
|
||||
if (s === exports.DOWN) {
|
||||
return exports.UP;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
exports.flipStatus = flipStatus;
|
||||
function sleep(ms) {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
exports.sleep = sleep;
|
||||
function ucfirst(str) {
|
||||
if (!str) {
|
||||
return str;
|
||||
}
|
||||
const firstLetter = str.substr(0, 1);
|
||||
return firstLetter.toUpperCase() + str.substr(1);
|
||||
}
|
||||
exports.ucfirst = ucfirst;
|
||||
function debug(msg) {
|
||||
exports.log.log("", "DEBUG", msg);
|
||||
}
|
||||
exports.debug = debug;
|
||||
class Logger {
|
||||
constructor() {
|
||||
this.hideLog = {
|
||||
info: [],
|
||||
warn: [],
|
||||
error: [],
|
||||
debug: [],
|
||||
};
|
||||
if (typeof process !== "undefined" && process.env.UPTIME_KUMA_HIDE_LOG) {
|
||||
const list = process.env.UPTIME_KUMA_HIDE_LOG.split(",").map((v) => v.toLowerCase());
|
||||
for (const pair of list) {
|
||||
const values = pair.split(/_(.*)/s);
|
||||
if (values.length >= 2) {
|
||||
this.hideLog[values[0]].push(values[1]);
|
||||
}
|
||||
}
|
||||
this.debug("server", "UPTIME_KUMA_HIDE_LOG is set");
|
||||
this.debug("server", this.hideLog);
|
||||
}
|
||||
}
|
||||
log(module, level, ...msg) {
|
||||
if (level === "DEBUG" && !exports.isDev) {
|
||||
return;
|
||||
}
|
||||
if (this.hideLog[level] && this.hideLog[level].includes(module.toLowerCase())) {
|
||||
return;
|
||||
}
|
||||
module = module.toUpperCase();
|
||||
let now;
|
||||
if (dayjs.tz) {
|
||||
now = dayjs.tz(new Date()).format();
|
||||
}
|
||||
else {
|
||||
now = dayjs().format();
|
||||
}
|
||||
if (process.env.UPTIME_KUMA_LOG_FORMAT === "json") {
|
||||
const msgString = msg
|
||||
.map((m) => {
|
||||
if (typeof m === "string") {
|
||||
return m;
|
||||
}
|
||||
else {
|
||||
try {
|
||||
return JSON.stringify(m);
|
||||
}
|
||||
catch (_a) {
|
||||
return String(m);
|
||||
}
|
||||
}
|
||||
})
|
||||
.join(" ");
|
||||
console.log(JSON.stringify({
|
||||
time: now,
|
||||
module: module,
|
||||
level: level,
|
||||
msg: msgString,
|
||||
}));
|
||||
return;
|
||||
}
|
||||
const levelColor = consoleLevelColors[level];
|
||||
const moduleColor = consoleModuleColors[intHash(module, consoleModuleColors.length)];
|
||||
let timePart;
|
||||
let modulePart;
|
||||
let levelPart;
|
||||
if (exports.isNode) {
|
||||
switch (level) {
|
||||
case "DEBUG":
|
||||
timePart = exports.CONSOLE_STYLE_FgGray + now + exports.CONSOLE_STYLE_Reset;
|
||||
break;
|
||||
default:
|
||||
timePart = exports.CONSOLE_STYLE_FgCyan + now + exports.CONSOLE_STYLE_Reset;
|
||||
break;
|
||||
}
|
||||
modulePart = "[" + moduleColor + module + exports.CONSOLE_STYLE_Reset + "]";
|
||||
levelPart = levelColor + `${level}:` + exports.CONSOLE_STYLE_Reset;
|
||||
}
|
||||
else {
|
||||
timePart = now;
|
||||
modulePart = `[${module}]`;
|
||||
levelPart = `${level}:`;
|
||||
}
|
||||
switch (level) {
|
||||
case "ERROR":
|
||||
console.error(timePart, modulePart, levelPart, ...msg);
|
||||
break;
|
||||
case "WARN":
|
||||
console.warn(timePart, modulePart, levelPart, ...msg);
|
||||
break;
|
||||
case "INFO":
|
||||
console.info(timePart, modulePart, levelPart, ...msg);
|
||||
break;
|
||||
case "DEBUG":
|
||||
if (exports.isDev) {
|
||||
console.debug(timePart, modulePart, levelPart, ...msg);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
console.log(timePart, modulePart, levelPart, ...msg);
|
||||
break;
|
||||
}
|
||||
}
|
||||
info(module, ...msg) {
|
||||
this.log(module, "INFO", ...msg);
|
||||
}
|
||||
warn(module, ...msg) {
|
||||
this.log(module, "WARN", ...msg);
|
||||
}
|
||||
error(module, ...msg) {
|
||||
this.log(module, "ERROR", ...msg);
|
||||
}
|
||||
debug(module, ...msg) {
|
||||
this.log(module, "DEBUG", ...msg);
|
||||
}
|
||||
exception(module, exception, ...msg) {
|
||||
this.log(module, "ERROR", ...msg, exception);
|
||||
}
|
||||
}
|
||||
exports.log = new Logger();
|
||||
function polyfill() {
|
||||
if (!String.prototype.replaceAll) {
|
||||
String.prototype.replaceAll = function (str, newStr) {
|
||||
if (Object.prototype.toString.call(str).toLowerCase() === "[object regexp]") {
|
||||
return this.replace(str, newStr);
|
||||
}
|
||||
return this.replace(new RegExp(str, "g"), newStr);
|
||||
};
|
||||
}
|
||||
}
|
||||
exports.polyfill = polyfill;
|
||||
class TimeLogger {
|
||||
constructor() {
|
||||
this.startTime = dayjs().valueOf();
|
||||
}
|
||||
print(name) {
|
||||
if (exports.isDev && process.env.TIMELOGGER === "1") {
|
||||
console.log(name + ": " + (dayjs().valueOf() - this.startTime) + "ms");
|
||||
}
|
||||
}
|
||||
}
|
||||
exports.TimeLogger = TimeLogger;
|
||||
function getRandomArbitrary(min, max) {
|
||||
return Math.random() * (max - min) + min;
|
||||
}
|
||||
exports.getRandomArbitrary = getRandomArbitrary;
|
||||
function getRandomInt(min, max) {
|
||||
min = Math.ceil(min);
|
||||
max = Math.floor(max);
|
||||
return Math.floor(Math.random() * (max - min + 1)) + min;
|
||||
}
|
||||
exports.getRandomInt = getRandomInt;
|
||||
const getRandomBytes = (typeof window !== "undefined" && window.crypto
|
||||
?
|
||||
function () {
|
||||
return (numBytes) => {
|
||||
const randomBytes = new Uint8Array(numBytes);
|
||||
for (let i = 0; i < numBytes; i += 65536) {
|
||||
window.crypto.getRandomValues(randomBytes.subarray(i, i + Math.min(numBytes - i, 65536)));
|
||||
}
|
||||
return randomBytes;
|
||||
};
|
||||
}
|
||||
:
|
||||
function () {
|
||||
return require("crypto").randomBytes;
|
||||
})();
|
||||
function getCryptoRandomInt(min, max) {
|
||||
const range = max - min;
|
||||
if (range >= Math.pow(2, 32)) {
|
||||
console.log("Warning! Range is too large.");
|
||||
}
|
||||
let tmpRange = range;
|
||||
let bitsNeeded = 0;
|
||||
let bytesNeeded = 0;
|
||||
let mask = 1;
|
||||
while (tmpRange > 0) {
|
||||
if (bitsNeeded % 8 === 0) {
|
||||
bytesNeeded += 1;
|
||||
}
|
||||
bitsNeeded += 1;
|
||||
mask = (mask << 1) | 1;
|
||||
tmpRange = tmpRange >>> 1;
|
||||
}
|
||||
const randomBytes = getRandomBytes(bytesNeeded);
|
||||
let randomValue = 0;
|
||||
for (let i = 0; i < bytesNeeded; i++) {
|
||||
randomValue |= randomBytes[i] << (8 * i);
|
||||
}
|
||||
randomValue = randomValue & mask;
|
||||
if (randomValue <= range) {
|
||||
return min + randomValue;
|
||||
}
|
||||
else {
|
||||
return getCryptoRandomInt(min, max);
|
||||
}
|
||||
}
|
||||
exports.getCryptoRandomInt = getCryptoRandomInt;
|
||||
function genSecret(length = 64) {
|
||||
let secret = "";
|
||||
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||
const charsLength = chars.length;
|
||||
for (let i = 0; i < length; i++) {
|
||||
secret += chars.charAt(getCryptoRandomInt(0, charsLength - 1));
|
||||
}
|
||||
return secret;
|
||||
}
|
||||
exports.genSecret = genSecret;
|
||||
function getMonitorRelativeURL(id) {
|
||||
return "/dashboard/" + id;
|
||||
}
|
||||
exports.getMonitorRelativeURL = getMonitorRelativeURL;
|
||||
function parseTimeObject(time) {
|
||||
if (!time) {
|
||||
return {
|
||||
hours: 0,
|
||||
minutes: 0,
|
||||
};
|
||||
}
|
||||
const array = time.split(":");
|
||||
if (array.length < 2) {
|
||||
throw new Error("parseVueDatePickerTimeFormat: Invalid Time");
|
||||
}
|
||||
const obj = {
|
||||
hours: parseInt(array[0]),
|
||||
minutes: parseInt(array[1]),
|
||||
seconds: 0,
|
||||
};
|
||||
if (array.length >= 3) {
|
||||
obj.seconds = parseInt(array[2]);
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
exports.parseTimeObject = parseTimeObject;
|
||||
function parseTimeFromTimeObject(obj) {
|
||||
if (!obj) {
|
||||
return obj;
|
||||
}
|
||||
let result = "";
|
||||
result += obj.hours.toString().padStart(2, "0") + ":" + obj.minutes.toString().padStart(2, "0");
|
||||
if (obj.seconds) {
|
||||
result += ":" + obj.seconds.toString().padStart(2, "0");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
exports.parseTimeFromTimeObject = parseTimeFromTimeObject;
|
||||
function isoToUTCDateTime(input) {
|
||||
return dayjs(input).utc().format(exports.SQL_DATETIME_FORMAT);
|
||||
}
|
||||
exports.isoToUTCDateTime = isoToUTCDateTime;
|
||||
function utcToISODateTime(input) {
|
||||
return dayjs.utc(input).toISOString();
|
||||
}
|
||||
exports.utcToISODateTime = utcToISODateTime;
|
||||
function utcToLocal(input, format = exports.SQL_DATETIME_FORMAT) {
|
||||
return dayjs.utc(input).local().format(format);
|
||||
}
|
||||
exports.utcToLocal = utcToLocal;
|
||||
function localToUTC(input, format = exports.SQL_DATETIME_FORMAT) {
|
||||
return dayjs(input).utc().format(format);
|
||||
}
|
||||
exports.localToUTC = localToUTC;
|
||||
function intHash(str, length = 10) {
|
||||
let hash = 0;
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
hash += str.charCodeAt(i);
|
||||
}
|
||||
return ((hash % length) + length) % length;
|
||||
}
|
||||
exports.intHash = intHash;
|
||||
async function evaluateJsonQuery(data, jsonPath, jsonPathOperator, expectedValue) {
|
||||
let response;
|
||||
try {
|
||||
response = JSON.parse(data);
|
||||
}
|
||||
catch (_a) {
|
||||
response =
|
||||
(typeof data === "object" || typeof data === "number") && !Buffer.isBuffer(data) ? data : data.toString();
|
||||
}
|
||||
try {
|
||||
response = jsonPath ? await jsonata(jsonPath).evaluate(response) : response;
|
||||
if (response === null || response === undefined) {
|
||||
throw new Error("Empty or undefined response. Check query syntax and response structure");
|
||||
}
|
||||
if (Array.isArray(response)) {
|
||||
const responseStr = JSON.stringify(response);
|
||||
const truncatedResponse = responseStr.length > 25 ? responseStr.substring(0, 25) + "...]" : responseStr;
|
||||
throw new Error("JSON query returned the array " +
|
||||
truncatedResponse +
|
||||
", but a primitive value is required. " +
|
||||
"Modify your query to return a single value via [0] to get the first element or use an aggregation like $count(), $sum() or $boolean().");
|
||||
}
|
||||
if (typeof response === "object" || response instanceof Date || typeof response === "function") {
|
||||
throw new Error(`The post-JSON query evaluated response from the server is of type ${typeof response}, which cannot be directly compared to the expected value`);
|
||||
}
|
||||
let jsonQueryExpression;
|
||||
switch (jsonPathOperator) {
|
||||
case ">":
|
||||
case ">=":
|
||||
case "<":
|
||||
case "<=":
|
||||
jsonQueryExpression = `$number($.value) ${jsonPathOperator} $number($.expected)`;
|
||||
break;
|
||||
case "!=":
|
||||
jsonQueryExpression = "$.value != $.expected";
|
||||
break;
|
||||
case "==":
|
||||
jsonQueryExpression = "$.value = $.expected";
|
||||
break;
|
||||
case "contains":
|
||||
jsonQueryExpression = "$contains($.value, $.expected)";
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Invalid condition ${jsonPathOperator}`);
|
||||
}
|
||||
const expression = jsonata(jsonQueryExpression);
|
||||
const status = await expression.evaluate({
|
||||
value: response.toString(),
|
||||
expected: expectedValue.toString(),
|
||||
});
|
||||
if (status === undefined) {
|
||||
throw new Error("Query evaluation returned undefined. Check query syntax and the structure of the response data");
|
||||
}
|
||||
return {
|
||||
status,
|
||||
response,
|
||||
};
|
||||
}
|
||||
catch (err) {
|
||||
response = JSON.stringify(response);
|
||||
response = response && response.length > 50 ? `${response.substring(0, 100)}… (truncated)` : response;
|
||||
throw new Error(`Error evaluating JSON query: ${err.message}. Response from server was: ${response}`);
|
||||
}
|
||||
}
|
||||
exports.evaluateJsonQuery = evaluateJsonQuery;
|
||||
exports.TYPES_WITH_DOMAIN_EXPIRY_SUPPORT_VIA_FIELD = {
|
||||
http: "url",
|
||||
keyword: "url",
|
||||
"json-query": "url",
|
||||
"real-browser": "url",
|
||||
"websocket-upgrade": "url",
|
||||
port: "hostname",
|
||||
ping: "hostname",
|
||||
"grpc-keyword": "grpcUrl",
|
||||
dns: "hostname",
|
||||
smtp: "hostname",
|
||||
snmp: "hostname",
|
||||
gamedig: "hostname",
|
||||
steam: "hostname",
|
||||
mqtt: "hostname",
|
||||
radius: "hostname",
|
||||
"tailscale-ping": "hostname",
|
||||
"sip-options": "hostname",
|
||||
};
|
||||
@@ -17,7 +17,7 @@ import * as timezone from "dayjs/plugin/timezone";
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
import * as utc from "dayjs/plugin/utc";
|
||||
|
||||
import * as jsonata from "jsonata";
|
||||
import jsonata from "jsonata";
|
||||
|
||||
export const isDev = process.env.NODE_ENV === "development";
|
||||
export const isNode = typeof process !== "undefined" && process?.versions?.node;
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
const { describe, test } = require("node:test");
|
||||
const assert = require("node:assert");
|
||||
|
||||
/**
|
||||
* Extracts the ping value filtering logic from PingChart.vue pushDatapoint().
|
||||
* This mirrors the condition: datapoint.up > 0 && datapoint.avgPing != null
|
||||
* @param {object} datapoint Datapoint with up, avgPing, minPing, maxPing
|
||||
* @returns {number|null} The avgPing value or null if filtered out
|
||||
*/
|
||||
function filterPingValue(datapoint) {
|
||||
return datapoint.up > 0 && datapoint.avgPing != null ? datapoint.avgPing : null;
|
||||
}
|
||||
|
||||
describe("PingChart pushDatapoint filtering", () => {
|
||||
test("avgPing of 0 should be rendered, not filtered out (#7143)", () => {
|
||||
const datapoint = { up: 1, down: 0, avgPing: 0, minPing: 0, maxPing: 0 };
|
||||
const result = filterPingValue(datapoint);
|
||||
assert.strictEqual(result, 0, "avgPing of 0 must not be converted to null");
|
||||
});
|
||||
|
||||
test("avgPing of 1 should be rendered", () => {
|
||||
const datapoint = { up: 1, down: 0, avgPing: 1, minPing: 1, maxPing: 1 };
|
||||
const result = filterPingValue(datapoint);
|
||||
assert.strictEqual(result, 1);
|
||||
});
|
||||
|
||||
test("avgPing of null should be filtered out", () => {
|
||||
const datapoint = { up: 1, down: 0, avgPing: null, minPing: null, maxPing: null };
|
||||
const result = filterPingValue(datapoint);
|
||||
assert.strictEqual(result, null);
|
||||
});
|
||||
|
||||
test("avgPing of undefined should be filtered out", () => {
|
||||
const datapoint = { up: 1, down: 0, avgPing: undefined, minPing: undefined, maxPing: undefined };
|
||||
const result = filterPingValue(datapoint);
|
||||
assert.strictEqual(result, null);
|
||||
});
|
||||
|
||||
test("datapoint with no up counts should be filtered out", () => {
|
||||
const datapoint = { up: 0, down: 1, avgPing: 5, minPing: 5, maxPing: 5 };
|
||||
const result = filterPingValue(datapoint);
|
||||
assert.strictEqual(result, null);
|
||||
});
|
||||
|
||||
test("normal ping value with up count should be rendered", () => {
|
||||
const datapoint = { up: 3, down: 0, avgPing: 42, minPing: 30, maxPing: 55 };
|
||||
const result = filterPingValue(datapoint);
|
||||
assert.strictEqual(result, 42);
|
||||
});
|
||||
});
|
||||
@@ -1,12 +0,0 @@
|
||||
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" });
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"esModuleInterop": false
|
||||
},
|
||||
"files": ["./src/util.ts"]
|
||||
}
|
||||
Reference in New Issue
Block a user