mirror of
https://github.com/louislam/uptime-kuma.git
synced 2025-12-05 01:10:52 +00:00
Compare commits
45 Commits
492b8f51ad
...
fa2bc8eda6
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fa2bc8eda6 | ||
|
|
93fc8e463f | ||
|
|
6dfa574e36 | ||
|
|
08d77e6fce | ||
|
|
5207ba6d97 | ||
|
|
a52186cf7e | ||
|
|
9fb4263427 | ||
|
|
79c844d598 | ||
|
|
7f294c2c25 | ||
|
|
e505cb56b4 | ||
|
|
d170e54a00 | ||
|
|
acda7f720f | ||
|
|
3466f8e9f7 | ||
|
|
18cb1ec9c0 | ||
|
|
ed18d06cbb | ||
|
|
34dcbf9d69 | ||
|
|
da257248ef | ||
|
|
a8b4d35b2b | ||
|
|
592b4cd712 | ||
|
|
b40bbdf68a | ||
|
|
14aaa10251 | ||
|
|
18813f90c8 | ||
|
|
76c4b4649a | ||
|
|
3b3eadf298 | ||
|
|
c608917611 | ||
|
|
4aec8c8b44 | ||
|
|
b688fe07a7 | ||
|
|
e9d061f9a8 | ||
|
|
48b25fa395 | ||
|
|
6e26b3ef54 | ||
|
|
fcb2b923e0 | ||
|
|
19c2bbd586 | ||
|
|
38ec3bc432 | ||
|
|
ea3a4f6963 | ||
|
|
a3672a6afb | ||
|
|
afbd1ce0e9 | ||
|
|
8f3cb770eb | ||
|
|
f6a47f351c | ||
|
|
93945606ea | ||
|
|
c3a62f7461 | ||
|
|
7bf25ba1bf | ||
|
|
b7bb961eac | ||
|
|
83c3cfc8c0 | ||
|
|
cd49700d3f | ||
|
|
9a3613856c |
167
.github/copilot-instructions.md
vendored
Normal file
167
.github/copilot-instructions.md
vendored
Normal file
@@ -0,0 +1,167 @@
|
||||
# Copilot Instructions for Uptime Kuma
|
||||
|
||||
## Copilot's Goals/Tasks
|
||||
|
||||
- Check spelling
|
||||
- Do not show "Pull Request Overview"
|
||||
- You do not have to reply if there are no issues
|
||||
|
||||
## Repository Overview
|
||||
|
||||
**Uptime Kuma** is a self-hosted monitoring tool for HTTP(s), TCP, DNS, Docker, etc. Built with Vue 3 (frontend) and Node.js/Express (backend), using Socket.IO for real-time communication.
|
||||
|
||||
- **Languages**: JavaScript, Vue 3, TypeScript (limited), HTML, CSS/SCSS
|
||||
- **Backend**: Node.js >= 20.4, Express.js, Socket.IO, SQLite
|
||||
- **Frontend**: Vue 3, Vite, Bootstrap 5, Chart.js
|
||||
- **Package Manager**: npm with `legacy-peer-deps=true` (.npmrc)
|
||||
|
||||
## Build & Validation Commands
|
||||
|
||||
### Prerequisites
|
||||
- Node.js >= 20.4.0, npm >= 9.3, Git
|
||||
|
||||
### Essential Command Sequence
|
||||
|
||||
1. **Install Dependencies**:
|
||||
```bash
|
||||
npm ci # Use npm ci NOT npm install (~60-90 seconds)
|
||||
```
|
||||
|
||||
2. **Linting** (required before committing):
|
||||
```bash
|
||||
npm run lint # Both linters (~15-30 seconds)
|
||||
npm run lint:prod # For production (zero warnings)
|
||||
```
|
||||
|
||||
3. **Build Frontend**:
|
||||
```bash
|
||||
npm run build # Takes ~90-120 seconds, builds to dist/
|
||||
```
|
||||
|
||||
4. **Run Tests**:
|
||||
```bash
|
||||
npm run test-backend # Backend tests (~50-60 seconds)
|
||||
npm test # All tests
|
||||
```
|
||||
|
||||
### Development Workflow
|
||||
|
||||
```bash
|
||||
npm run dev # Starts frontend (port 3000) and backend (port 3001)
|
||||
```
|
||||
|
||||
## Project Architecture
|
||||
|
||||
### Directory Structure
|
||||
|
||||
```
|
||||
/
|
||||
├── server/ Backend source code
|
||||
│ ├── model/ Database models (auto-mapped to tables)
|
||||
│ ├── monitor-types/ Monitor type implementations
|
||||
│ ├── notification-providers/ Notification integrations
|
||||
│ ├── routers/ Express routers
|
||||
│ ├── socket-handlers/ Socket.IO event handlers
|
||||
│ ├── server.js Server entry point
|
||||
│ └── uptime-kuma-server.js Main server logic
|
||||
├── src/ Frontend source code (Vue 3 SPA)
|
||||
│ ├── components/ Vue components
|
||||
│ ├── pages/ Page components
|
||||
│ ├── lang/ i18n translations
|
||||
│ ├── router.js Vue Router configuration
|
||||
│ └── main.js Frontend entry point
|
||||
├── db/ Database related
|
||||
│ ├── knex_migrations/ Knex migration files
|
||||
│ └── kuma.db SQLite database (gitignored)
|
||||
├── test/ Test files
|
||||
│ ├── backend-test/ Backend unit tests
|
||||
│ └── e2e/ Playwright E2E tests
|
||||
├── config/ Build configuration
|
||||
│ ├── vite.config.js Vite build config
|
||||
│ └── playwright.config.js Playwright test config
|
||||
├── dist/ Frontend build output (gitignored)
|
||||
├── data/ App data directory (gitignored)
|
||||
├── public/ Static frontend assets (dev only)
|
||||
├── docker/ Docker build files
|
||||
└── extra/ Utility scripts
|
||||
```
|
||||
|
||||
### Key Configuration Files
|
||||
|
||||
- **package.json**: Scripts, dependencies, Node.js version requirement
|
||||
- **.eslintrc.js**: ESLint rules (4 spaces, double quotes, unix line endings, JSDoc required)
|
||||
- **.stylelintrc**: Stylelint rules (4 spaces indentation)
|
||||
- **.editorconfig**: Editor settings (4 spaces, LF, UTF-8)
|
||||
- **tsconfig-backend.json**: TypeScript config for backend (only src/util.ts)
|
||||
- **.npmrc**: `legacy-peer-deps=true` (required for dependency resolution)
|
||||
- **.gitignore**: Excludes node_modules, dist, data, tmp, private
|
||||
|
||||
### Code Style (strictly enforced by linters)
|
||||
|
||||
- 4 spaces indentation, double quotes, Unix line endings (LF), semicolons required
|
||||
- **Naming**: JavaScript/TypeScript (camelCase), SQLite (snake_case), CSS/SCSS (kebab-case)
|
||||
- JSDoc required for all functions/methods
|
||||
|
||||
## CI/CD Workflows
|
||||
|
||||
**auto-test.yml** (runs on PR/push to master/1.23.X):
|
||||
- Linting, building, backend tests on multiple OS/Node versions (15 min timeout)
|
||||
- E2E Playwright tests
|
||||
|
||||
**validate.yml**: Validates JSON/YAML files, language files, knex migrations
|
||||
|
||||
**PR Requirements**: All linters pass, tests pass, code follows style guidelines
|
||||
|
||||
## Common Issues
|
||||
|
||||
1. **npm install vs npm ci**: Always use `npm ci` for reproducible builds
|
||||
2. **TypeScript errors**: `npm run tsc` shows 1400+ errors - ignore them, they don't affect builds
|
||||
3. **Stylelint warnings**: Deprecation warnings are expected, ignore them
|
||||
4. **Test failures**: Always run `npm run build` before running tests
|
||||
5. **Port conflicts**: Dev server uses ports 3000 and 3001
|
||||
6. **First run**: Server shows "db-config.json not found" - this is expected, starts setup wizard
|
||||
|
||||
## Translations
|
||||
|
||||
- Managed via Weblate. Add keys to `src/lang/en.json` only
|
||||
- Don't include other languages in PRs
|
||||
- Use `$t("key")` in Vue templates
|
||||
|
||||
## Database
|
||||
|
||||
- Primary: SQLite (also supports MariaDB/MySQL/PostgreSQL)
|
||||
- Migrations in `db/knex_migrations/` using Knex.js
|
||||
- Filename format validated by CI: `node ./extra/check-knex-filenames.mjs`
|
||||
|
||||
## Testing
|
||||
|
||||
- **Backend**: Node.js test runner, fast unit tests
|
||||
- **E2E**: Playwright (requires `npx playwright install` first time)
|
||||
- Test data in `data/playwright-test`
|
||||
|
||||
## Adding New Features
|
||||
|
||||
### New Notification Provider
|
||||
Files to modify:
|
||||
1. `server/notification-providers/PROVIDER_NAME.js` (backend logic)
|
||||
2. `server/notification.js` (register provider)
|
||||
3. `src/components/notifications/PROVIDER_NAME.vue` (frontend UI)
|
||||
4. `src/components/notifications/index.js` (register frontend)
|
||||
5. `src/components/NotificationDialog.vue` (add to list)
|
||||
6. `src/lang/en.json` (add translation keys)
|
||||
|
||||
### New Monitor Type
|
||||
Files to modify:
|
||||
1. `server/monitor-types/MONITORING_TYPE.js` (backend logic)
|
||||
2. `server/uptime-kuma-server.js` (register monitor type)
|
||||
3. `src/pages/EditMonitor.vue` (frontend UI)
|
||||
4. `src/lang/en.json` (add translation keys)
|
||||
|
||||
## Important Notes
|
||||
|
||||
1. **Trust these instructions** - based on testing. Search only if incomplete/incorrect
|
||||
2. **Dependencies**: 5 known vulnerabilities (3 moderate, 2 high) - acknowledged, don't fix without discussion
|
||||
3. **Git Branches**: `master` (v2 development), `1.23.X` (v1 maintenance)
|
||||
4. **Node Version**: >= 20.4.0 required
|
||||
5. **Socket.IO**: Most backend logic in `server/socket-handlers/`, not REST
|
||||
6. **Never commit**: `data/`, `dist/`, `tmp/`, `private/`, `node_modules/`
|
||||
42
.github/workflows/auto-test.yml
vendored
42
.github/workflows/auto-test.yml
vendored
@@ -5,11 +5,11 @@ name: Auto Test
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master, 1.23.X ]
|
||||
branches: [ master, 1.23.X, 3.0.X ]
|
||||
paths-ignore:
|
||||
- '*.md'
|
||||
pull_request:
|
||||
branches: [ master, 1.23.X ]
|
||||
branches: [ master, 1.23.X, 3.0.X ]
|
||||
paths-ignore:
|
||||
- '*.md'
|
||||
|
||||
@@ -22,15 +22,15 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [macos-latest, ubuntu-22.04, windows-latest, ARM64]
|
||||
node: [ 18, 20 ]
|
||||
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
|
||||
node: [ 24, 25 ]
|
||||
|
||||
steps:
|
||||
- run: git config --global core.autocrlf false # Mainly for Windows
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Use Node.js ${{ matrix.node }}
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: ${{ matrix.node }}
|
||||
- run: npm install
|
||||
@@ -40,28 +40,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:
|
||||
needs: [ ]
|
||||
runs-on: ${{ matrix.os }}
|
||||
timeout-minutes: 15
|
||||
if: ${{ github.repository == 'louislam/uptime-kuma' }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ ARMv7 ]
|
||||
node: [ 18, 20 ]
|
||||
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
|
||||
|
||||
steps:
|
||||
- run: git config --global core.autocrlf false # Mainly for Windows
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Use Node.js ${{ matrix.node }}
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ matrix.node }}
|
||||
- run: npm ci --production
|
||||
|
||||
check-linters:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
@@ -69,10 +47,10 @@ jobs:
|
||||
- run: git config --global core.autocrlf false # Mainly for Windows
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Use Node.js 20
|
||||
uses: actions/setup-node@v4
|
||||
- name: Use Node.js 24
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 20
|
||||
node-version: 24
|
||||
- run: npm install
|
||||
- run: npm run lint:prod
|
||||
|
||||
@@ -83,10 +61,10 @@ jobs:
|
||||
- run: git config --global core.autocrlf false # Mainly for Windows
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Use Node.js 20
|
||||
uses: actions/setup-node@v4
|
||||
- name: Use Node.js 24
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 20
|
||||
node-version: 24
|
||||
- run: npm install
|
||||
- run: npx playwright install
|
||||
- run: npm run build
|
||||
|
||||
4
.github/workflows/close-incorrect-issue.yml
vendored
4
.github/workflows/close-incorrect-issue.yml
vendored
@@ -11,13 +11,13 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest]
|
||||
node-version: [18]
|
||||
node-version: [20]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
cache: 'npm'
|
||||
|
||||
7
.github/workflows/validate.yml
vendored
7
.github/workflows/validate.yml
vendored
@@ -7,6 +7,7 @@ on:
|
||||
branches:
|
||||
- master
|
||||
- 1.23.X
|
||||
- 3.0.X
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
@@ -31,10 +32,10 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Use Node.js 20
|
||||
uses: actions/setup-node@v4
|
||||
- name: Use Node.js 24
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 20
|
||||
node-version: 24
|
||||
|
||||
- name: Validate language JSON files
|
||||
run: node ./extra/check-lang-json.js
|
||||
|
||||
@@ -447,7 +447,7 @@ 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
|
||||
|
||||
@@ -639,7 +639,6 @@ repo to do that.
|
||||
|
||||
- amd64
|
||||
- arm64
|
||||
- armv7
|
||||
|
||||
### Docker Tags
|
||||
|
||||
@@ -692,7 +691,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.
|
||||
|
||||
@@ -707,7 +706,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
|
||||
```
|
||||
@@ -742,8 +741,7 @@ We have a few procedures we follow. These are documented here:
|
||||
|
||||
- [ ] 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>
|
||||
|
||||
22
README.md
22
README.md
@@ -45,8 +45,7 @@ cd uptime-kuma
|
||||
curl -o compose.yaml https://raw.githubusercontent.com/louislam/uptime-kuma/master/compose.yaml
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
Uptime Kuma is now running on <http://0.0.0.0:3001>.
|
||||
Uptime Kuma is now running on all network interfaces (e.g. http://localhost:3001 or http://your-ip:3001).
|
||||
|
||||
> [!WARNING]
|
||||
> File Systems like **NFS** (Network File System) are **NOT** supported. Please map to a local directory or volume.
|
||||
@@ -56,22 +55,22 @@ Uptime Kuma is now running on <http://0.0.0.0:3001>.
|
||||
```bash
|
||||
docker run -d --restart=always -p 3001:3001 -v uptime-kuma:/app/data --name uptime-kuma louislam/uptime-kuma:2
|
||||
```
|
||||
Uptime Kuma is now running on all network interfaces (e.g. http://localhost:3001 or http://your-ip:3001).
|
||||
|
||||
If you want to limit exposure to localhost only:
|
||||
|
||||
```bash
|
||||
docker run ... -p 127.0.0.1:3001:3001 ...
|
||||
```
|
||||
|
||||
Uptime Kuma is now running on <http://0.0.0.0:3001>.
|
||||
|
||||
> [!NOTE]
|
||||
> If you want to limit exposure to localhost (without exposing port for other users or to use a [reverse proxy](https://github.com/louislam/uptime-kuma/wiki/Reverse-Proxy)), you can expose the port like this:
|
||||
>
|
||||
> ```bash
|
||||
> docker run -d --restart=always -p 127.0.0.1:3001:3001 -v uptime-kuma:/app/data --name uptime-kuma louislam/uptime-kuma:2
|
||||
> ```
|
||||
|
||||
### 💪🏻 Non-Docker
|
||||
|
||||
Requirements:
|
||||
|
||||
- Platform
|
||||
- ✅ Major Linux distros such as Debian, Ubuntu, CentOS, Fedora and ArchLinux etc.
|
||||
- ✅ Major Linux distros such as Debian, Ubuntu, Fedora and ArchLinux etc.
|
||||
- ✅ Windows 10 (x64), Windows Server 2012 R2 (x64) or higher
|
||||
- ❌ FreeBSD / OpenBSD / NetBSD
|
||||
- ❌ Replit / Heroku
|
||||
@@ -94,8 +93,7 @@ npm install pm2 -g && pm2 install pm2-logrotate
|
||||
# Start Server
|
||||
pm2 start server/server.js --name uptime-kuma
|
||||
```
|
||||
|
||||
Uptime Kuma is now running on <http://localhost:3001>
|
||||
Uptime Kuma is now running on all network interfaces (e.g. http://localhost:3001 or http://your-ip:3001).
|
||||
|
||||
More useful PM2 Commands
|
||||
|
||||
|
||||
@@ -58,7 +58,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: "../",
|
||||
|
||||
@@ -2,7 +2,6 @@ import vue from "@vitejs/plugin-vue";
|
||||
import { defineConfig } from "vite";
|
||||
import visualizer from "rollup-plugin-visualizer";
|
||||
import viteCompression from "vite-plugin-compression";
|
||||
import VueDevTools from "vite-plugin-vue-devtools";
|
||||
|
||||
const postCssScss = require("postcss-scss");
|
||||
const postcssRTLCSS = require("postcss-rtlcss");
|
||||
@@ -31,7 +30,6 @@ export default defineConfig({
|
||||
algorithm: "brotliCompress",
|
||||
filter: viteCompressionFilter,
|
||||
}),
|
||||
VueDevTools(),
|
||||
],
|
||||
css: {
|
||||
postcss: {
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
############################################
|
||||
# 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-bookworm
|
||||
FROM golang:1-trixie
|
||||
WORKDIR /app
|
||||
ARG TARGETPLATFORM
|
||||
COPY ./extra/ ./extra/
|
||||
@@ -10,7 +9,7 @@ COPY ./extra/ ./extra/
|
||||
# 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:20-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:20-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,21 +6,6 @@ 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();
|
||||
@@ -50,13 +50,13 @@ execSync("node ./extra/beta/update-version.js");
|
||||
buildDist();
|
||||
|
||||
// Build slim image (rootless)
|
||||
buildImage(repoNames, [ "beta-slim-rootless", ver(version, "slim-rootless") ], "rootless", "BASE_IMAGE=louislam/uptime-kuma:base2-slim");
|
||||
buildImage(repoNames, [ "beta-slim-rootless", ver(version, "slim-rootless") ], "rootless", "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");
|
||||
|
||||
@@ -40,16 +40,16 @@ execSync("node extra/update-version.js");
|
||||
buildDist();
|
||||
|
||||
// Build slim image (rootless)
|
||||
buildImage(repoNames, [ "2-slim-rootless", ver(version, "slim-rootless") ], "rootless", "BASE_IMAGE=louislam/uptime-kuma:base2-slim");
|
||||
buildImage(repoNames, [ "3-slim-rootless", ver(version, "slim-rootless") ], "rootless", "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") ], "release", "BASE_IMAGE=louislam/uptime-kuma:base2-slim");
|
||||
buildImage(repoNames, [ "next-slim", "3-slim", ver(version, "slim") ], "release", "BASE_IMAGE=louislam/uptime-kuma:base3-slim");
|
||||
|
||||
// Build full image
|
||||
buildImage(repoNames, [ "next", "2", version ], "release");
|
||||
buildImage(repoNames, [ "next", "3", version ], "release");
|
||||
|
||||
await pressAnyKey();
|
||||
|
||||
|
||||
@@ -57,7 +57,7 @@ export function buildDist() {
|
||||
* @param {string} platform Build platform
|
||||
* @returns {void}
|
||||
*/
|
||||
export function buildImage(repoNames, tags, target, buildArgs = "", dockerfile = "docker/dockerfile", platform = "linux/amd64,linux/arm64,linux/arm/v7") {
|
||||
export function buildImage(repoNames, tags, target, buildArgs = "", dockerfile = "docker/dockerfile", platform = "linux/amd64,linux/arm64") {
|
||||
let args = [
|
||||
"buildx",
|
||||
"build",
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
const fs = require("fs");
|
||||
|
||||
// Read the file from private/sort-contributors.txt
|
||||
const file = fs.readFileSync("private/sort-contributors.txt", "utf8");
|
||||
|
||||
// Convert to an array of lines
|
||||
let lines = file.split("\n");
|
||||
|
||||
// Remove empty lines
|
||||
lines = lines.filter((line) => line !== "");
|
||||
|
||||
// Remove duplicates
|
||||
lines = [ ...new Set(lines) ];
|
||||
|
||||
// Remove @weblate and @UptimeKumaBot
|
||||
lines = lines.filter((line) => line !== "@weblate" && line !== "@UptimeKumaBot" && line !== "@louislam");
|
||||
|
||||
// Sort the lines
|
||||
lines = lines.sort();
|
||||
|
||||
// Output the lines, concat with " "
|
||||
console.log(lines.join(" "));
|
||||
@@ -27,7 +27,18 @@ if (! exists) {
|
||||
|
||||
// Also update package-lock.json
|
||||
const npm = /^win/.test(process.platform) ? "npm.cmd" : "npm";
|
||||
childProcess.spawnSync(npm, [ "install" ]);
|
||||
const resultVersion = childProcess.spawnSync(npm, [ "--no-git-tag-version", "version", newVersion ], { shell: true });
|
||||
if (resultVersion.error) {
|
||||
console.error(resultVersion.error);
|
||||
console.error("error npm version!");
|
||||
process.exit(1);
|
||||
}
|
||||
const resultInstall = childProcess.spawnSync(npm, [ "install" ], { shell: true });
|
||||
if (resultInstall.error) {
|
||||
console.error(resultInstall.error);
|
||||
console.error("error update package-lock!");
|
||||
process.exit(1);
|
||||
}
|
||||
commit(newVersion);
|
||||
|
||||
} else {
|
||||
|
||||
1923
package-lock.json
generated
1923
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
36
package.json
36
package.json
@@ -1,13 +1,13 @@
|
||||
{
|
||||
"name": "uptime-kuma",
|
||||
"version": "2.0.1",
|
||||
"version": "3.0.0-beta.0",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/louislam/uptime-kuma.git"
|
||||
},
|
||||
"engines": {
|
||||
"node": "18 || >= 20.4.0"
|
||||
"node": ">= 24.0.0"
|
||||
},
|
||||
"scripts": {
|
||||
"lint:js": "eslint --ext \".js,.vue\" --ignore-path .gitignore .",
|
||||
@@ -19,29 +19,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": "cross-env TEST_BACKEND=1 node --test test/backend-test",
|
||||
"test-backend": "cross-env TEST_BACKEND=1 node --import=tsx --test \"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.0.1 && npm ci --omit dev && npm run download-dist",
|
||||
"setup": "git checkout 2.0.2 && 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",
|
||||
@@ -58,12 +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",
|
||||
"sort-contributors": "node extra/sort-contributors.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"
|
||||
},
|
||||
@@ -146,7 +141,9 @@
|
||||
"tcp-ping": "~0.1.1",
|
||||
"thirty-two": "~1.0.2",
|
||||
"tough-cookie": "~4.1.3",
|
||||
"ws": "^8.13.0"
|
||||
"tsx": "~4.20.6",
|
||||
"ws": "^8.13.0",
|
||||
"zod": "~4.1.12"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@actions/github": "~6.0.0",
|
||||
@@ -198,7 +195,6 @@
|
||||
"v-pagination-3": "~0.1.7",
|
||||
"vite": "~5.4.15",
|
||||
"vite-plugin-compression": "^0.5.1",
|
||||
"vite-plugin-vue-devtools": "^7.0.15",
|
||||
"vue": "~3.4.2",
|
||||
"vue-chartjs": "~5.2.0",
|
||||
"vue-confirm-dialog": "~1.0.2",
|
||||
|
||||
@@ -18,8 +18,8 @@ exports.login = async function (username, password) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let user = await R.findOne("user", " username = ? AND active = 1 ", [
|
||||
username,
|
||||
let user = await R.findOne("user", "TRIM(username) = ? AND active = 1 ", [
|
||||
username.trim(),
|
||||
]);
|
||||
|
||||
if (user && passwordHash.verify(password, user.password)) {
|
||||
|
||||
@@ -578,7 +578,8 @@ class Monitor extends BeanModel {
|
||||
}
|
||||
}
|
||||
|
||||
if (process.env.UPTIME_KUMA_LOG_RESPONSE_BODY_MONITOR_ID === this.id) {
|
||||
// eslint-disable-next-line eqeqeq
|
||||
if (process.env.UPTIME_KUMA_LOG_RESPONSE_BODY_MONITOR_ID == this.id) {
|
||||
log.info("monitor", res.data);
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ class MqttMonitorType extends MonitorType {
|
||||
* @inheritdoc
|
||||
*/
|
||||
async check(monitor, heartbeat, server) {
|
||||
const receivedMessage = await this.mqttAsync(monitor.hostname, monitor.mqttTopic, {
|
||||
const [ messageTopic, receivedMessage ] = await this.mqttAsync(monitor.hostname, monitor.mqttTopic, {
|
||||
port: monitor.port,
|
||||
username: monitor.mqttUsername,
|
||||
password: monitor.mqttPassword,
|
||||
@@ -25,7 +25,7 @@ class MqttMonitorType extends MonitorType {
|
||||
|
||||
if (monitor.mqttCheckType === "keyword") {
|
||||
if (receivedMessage != null && receivedMessage.includes(monitor.mqttSuccessMessage)) {
|
||||
heartbeat.msg = `Topic: ${monitor.mqttTopic}; Message: ${receivedMessage}`;
|
||||
heartbeat.msg = `Topic: ${messageTopic}; Message: ${receivedMessage}`;
|
||||
heartbeat.status = UP;
|
||||
} else {
|
||||
throw Error(`Message Mismatch - Topic: ${monitor.mqttTopic}; Message: ${receivedMessage}`);
|
||||
@@ -110,11 +110,9 @@ class MqttMonitorType extends MonitorType {
|
||||
});
|
||||
|
||||
client.on("message", (messageTopic, message) => {
|
||||
if (messageTopic === topic) {
|
||||
client.end();
|
||||
clearTimeout(timeoutID);
|
||||
resolve(message.toString("utf8"));
|
||||
}
|
||||
client.end();
|
||||
clearTimeout(timeoutID);
|
||||
resolve([ messageTopic, message.toString("utf8") ]);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -2,13 +2,13 @@ const { MonitorType } = require("./monitor-type");
|
||||
const { chromium } = require("playwright-core");
|
||||
const { UP, log } = require("../../src/util");
|
||||
const { Settings } = require("../settings");
|
||||
const commandExistsSync = require("command-exists").sync;
|
||||
const childProcess = require("child_process");
|
||||
const path = require("path");
|
||||
const Database = require("../database");
|
||||
const jwt = require("jsonwebtoken");
|
||||
const config = require("../config");
|
||||
const { RemoteBrowser } = require("../remote-browser");
|
||||
const { commandExists } = require("../util-server");
|
||||
|
||||
/**
|
||||
* Cached instance of a browser
|
||||
@@ -122,7 +122,7 @@ async function prepareChromeExecutable(executablePath) {
|
||||
executablePath = "/usr/bin/chromium";
|
||||
|
||||
// Install chromium in container via apt install
|
||||
if ( !commandExistsSync(executablePath)) {
|
||||
if (! await commandExists(executablePath)) {
|
||||
await new Promise((resolve, reject) => {
|
||||
log.info("Chromium", "Installing Chromium...");
|
||||
let child = childProcess.exec("apt update && apt --yes --no-install-recommends install chromium fonts-indic fonts-noto fonts-noto-cjk");
|
||||
@@ -146,7 +146,7 @@ async function prepareChromeExecutable(executablePath) {
|
||||
}
|
||||
|
||||
} else {
|
||||
executablePath = findChrome(allowedList);
|
||||
executablePath = await findChrome(allowedList);
|
||||
}
|
||||
} else {
|
||||
// User specified a path
|
||||
@@ -160,20 +160,20 @@ async function prepareChromeExecutable(executablePath) {
|
||||
|
||||
/**
|
||||
* Find the chrome executable
|
||||
* @param {any[]} executables Executables to search through
|
||||
* @returns {any} Executable
|
||||
* @throws Could not find executable
|
||||
* @param {string[]} executables Executables to search through
|
||||
* @returns {Promise<string>} Executable
|
||||
* @throws {Error} Could not find executable
|
||||
*/
|
||||
function findChrome(executables) {
|
||||
async function findChrome(executables) {
|
||||
// Use the last working executable, so we don't have to search for it again
|
||||
if (lastAutoDetectChromeExecutable) {
|
||||
if (commandExistsSync(lastAutoDetectChromeExecutable)) {
|
||||
if (await commandExists(lastAutoDetectChromeExecutable)) {
|
||||
return lastAutoDetectChromeExecutable;
|
||||
}
|
||||
}
|
||||
|
||||
for (let executable of executables) {
|
||||
if (commandExistsSync(executable)) {
|
||||
if (await commandExists(executable)) {
|
||||
lastAutoDetectChromeExecutable = executable;
|
||||
return executable;
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ class TailscalePing extends MonitorType {
|
||||
timeout: timeout,
|
||||
encoding: "utf8",
|
||||
});
|
||||
if (res.stderr && res.stderr.toString()) {
|
||||
if (res.stderr && res.stderr.toString() && res.code !== 0) {
|
||||
throw new Error(`Error in output: ${res.stderr.toString()}`);
|
||||
}
|
||||
if (res.stdout && res.stdout.toString()) {
|
||||
|
||||
@@ -12,6 +12,29 @@ class GoogleChat extends NotificationProvider {
|
||||
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
|
||||
const okMsg = "Sent Successfully.";
|
||||
|
||||
// If Google Chat Webhook rate limit is reached, retry to configured max retries defaults to 3, delay between 60-180 seconds
|
||||
const post = async (url, data, config) => {
|
||||
let retries = notification.googleChatMaxRetries || 1; // Default to 1 retries
|
||||
retries = (retries > 10) ? 10 : retries; // Enforce maximum retries in backend
|
||||
while (retries > 0) {
|
||||
try {
|
||||
await axios.post(url, data, config);
|
||||
return;
|
||||
} catch (error) {
|
||||
if (error.response && error.response.status === 429) {
|
||||
retries--;
|
||||
if (retries === 0) {
|
||||
throw error;
|
||||
}
|
||||
const delay = 60000 + Math.random() * 120000;
|
||||
await new Promise(resolve => setTimeout(resolve, delay));
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
let config = this.getAxiosConfigWithProxy({});
|
||||
// Google Chat message formatting: https://developers.google.com/chat/api/guides/message-formats/basic
|
||||
@@ -24,7 +47,7 @@ class GoogleChat extends NotificationProvider {
|
||||
heartbeatJSON
|
||||
);
|
||||
const data = { "text": renderedText };
|
||||
await axios.post(notification.googleChatWebhookURL, data, config);
|
||||
await post(notification.googleChatWebhookURL, data, config);
|
||||
return okMsg;
|
||||
}
|
||||
|
||||
@@ -96,7 +119,7 @@ class GoogleChat extends NotificationProvider {
|
||||
],
|
||||
};
|
||||
|
||||
await axios.post(notification.googleChatWebhookURL, data, config);
|
||||
await post(notification.googleChatWebhookURL, data, config);
|
||||
return okMsg;
|
||||
} catch (error) {
|
||||
this.throwGeneralAxiosError(error);
|
||||
|
||||
@@ -23,9 +23,7 @@ class PagerDuty extends NotificationProvider {
|
||||
|
||||
if (heartbeatJSON.status === UP) {
|
||||
const title = "Uptime Kuma Monitor ✅ Up";
|
||||
const eventAction = notification.pagerdutyAutoResolve || null;
|
||||
|
||||
return this.postNotification(notification, title, heartbeatJSON.msg, monitorJSON, eventAction);
|
||||
return this.postNotification(notification, title, heartbeatJSON.msg, monitorJSON, "resolve");
|
||||
}
|
||||
|
||||
if (heartbeatJSON.status === DOWN) {
|
||||
@@ -63,10 +61,6 @@ class PagerDuty extends NotificationProvider {
|
||||
*/
|
||||
async postNotification(notification, title, body, monitorInfo, eventAction = "trigger") {
|
||||
|
||||
if (eventAction == null) {
|
||||
return "No action required";
|
||||
}
|
||||
|
||||
let monitorUrl;
|
||||
if (monitorInfo.type === "port") {
|
||||
monitorUrl = monitorInfo.hostname;
|
||||
@@ -79,6 +73,13 @@ class PagerDuty extends NotificationProvider {
|
||||
monitorUrl = monitorInfo.url;
|
||||
}
|
||||
|
||||
if (eventAction === "resolve") {
|
||||
if (notification.pagerdutyAutoResolve === "0") {
|
||||
return "no action required";
|
||||
}
|
||||
eventAction = notification.pagerdutyAutoResolve;
|
||||
}
|
||||
|
||||
const options = {
|
||||
method: "POST",
|
||||
url: notification.pagerdutyIntegrationUrl,
|
||||
|
||||
@@ -12,6 +12,8 @@ class Webhook extends NotificationProvider {
|
||||
const okMsg = "Sent Successfully.";
|
||||
|
||||
try {
|
||||
const httpMethod = notification.httpMethod.toLowerCase() || "post";
|
||||
|
||||
let data = {
|
||||
heartbeat: heartbeatJSON,
|
||||
monitor: monitorJSON,
|
||||
@@ -21,7 +23,19 @@ class Webhook extends NotificationProvider {
|
||||
headers: {}
|
||||
};
|
||||
|
||||
if (notification.webhookContentType === "form-data") {
|
||||
if (httpMethod === "get") {
|
||||
config.params = {
|
||||
msg: msg
|
||||
};
|
||||
|
||||
if (heartbeatJSON) {
|
||||
config.params.heartbeat = JSON.stringify(heartbeatJSON);
|
||||
}
|
||||
|
||||
if (monitorJSON) {
|
||||
config.params.monitor = JSON.stringify(monitorJSON);
|
||||
}
|
||||
} else if (notification.webhookContentType === "form-data") {
|
||||
const formData = new FormData();
|
||||
formData.append("data", JSON.stringify(data));
|
||||
config.headers = formData.getHeaders();
|
||||
@@ -42,7 +56,13 @@ class Webhook extends NotificationProvider {
|
||||
}
|
||||
|
||||
config = this.getAxiosConfigWithProxy(config);
|
||||
await axios.post(notification.webhookURL, data, config);
|
||||
|
||||
if (httpMethod === "get") {
|
||||
await axios.get(notification.webhookURL, config);
|
||||
} else {
|
||||
await axios.post(notification.webhookURL, data, config);
|
||||
}
|
||||
|
||||
return okMsg;
|
||||
|
||||
} catch (error) {
|
||||
|
||||
@@ -81,6 +81,7 @@ const Brevo = require("./notification-providers/brevo");
|
||||
const YZJ = require("./notification-providers/yzj");
|
||||
const SMSPlanet = require("./notification-providers/sms-planet");
|
||||
const SpugPush = require("./notification-providers/spugpush");
|
||||
const { commandExists } = require("./util-server");
|
||||
|
||||
class Notification {
|
||||
providerList = {};
|
||||
@@ -275,12 +276,10 @@ class Notification {
|
||||
|
||||
/**
|
||||
* Check if apprise exists
|
||||
* @returns {boolean} Does the command apprise exist?
|
||||
* @returns {Promise<boolean>} Does the command apprise exist?
|
||||
*/
|
||||
static checkApprise() {
|
||||
let commandExistsSync = require("command-exists").sync;
|
||||
let exists = commandExistsSync("apprise");
|
||||
return exists;
|
||||
static async checkApprise() {
|
||||
return await commandExists("apprise");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -37,7 +39,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");
|
||||
|
||||
log.debug("server", "Arguments");
|
||||
@@ -59,7 +60,7 @@ if (process.env.UPTIME_KUMA_WS_ORIGIN_CHECK === "bypass") {
|
||||
}
|
||||
|
||||
const checkVersion = require("./check-version");
|
||||
log.info("server", "Uptime Kuma Version: " + checkVersion.version);
|
||||
log.info("server", "Uptime Kuma Version:", checkVersion.version);
|
||||
|
||||
log.info("server", "Loading modules");
|
||||
|
||||
@@ -1060,6 +1061,27 @@ let needSetup = false;
|
||||
|
||||
const startTime = Date.now();
|
||||
|
||||
// Check if this is a group monitor and unlink children before deletion
|
||||
const monitor = await R.findOne("monitor", " id = ? AND user_id = ? ", [
|
||||
monitorID,
|
||||
socket.userID,
|
||||
]);
|
||||
|
||||
if (monitor && monitor.type === "group") {
|
||||
// Get all children before unlinking them
|
||||
const children = await Monitor.getChildren(monitorID);
|
||||
|
||||
// Unlink all children from the group
|
||||
await Monitor.unlinkAllChildren(monitorID);
|
||||
|
||||
// Notify frontend to update each child monitor's parent to null
|
||||
if (children && children.length > 0) {
|
||||
for (const child of children) {
|
||||
await server.sendUpdateMonitorIntoList(socket, child.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await R.exec("DELETE FROM monitor WHERE id = ? AND user_id = ? ", [
|
||||
monitorID,
|
||||
socket.userID,
|
||||
@@ -1503,7 +1525,7 @@ let needSetup = false;
|
||||
socket.on("checkApprise", async (callback) => {
|
||||
try {
|
||||
checkLogin(socket);
|
||||
callback(Notification.checkApprise());
|
||||
callback(await Notification.checkApprise());
|
||||
} catch (e) {
|
||||
callback(false);
|
||||
}
|
||||
|
||||
@@ -1116,3 +1116,19 @@ function fsExists(path) {
|
||||
});
|
||||
}
|
||||
module.exports.fsExists = fsExists;
|
||||
|
||||
/**
|
||||
* By default, command-exists will throw a null error if the command does not exist, which is ugly. The function makes it better.
|
||||
* Read more: https://github.com/mathisonian/command-exists/issues/22
|
||||
* @param {string} command Command to check
|
||||
* @returns {Promise<boolean>} True if command exists, false otherwise
|
||||
*/
|
||||
async function commandExists(command) {
|
||||
try {
|
||||
await require("command-exists")(command);
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
module.exports.commandExists = commandExists;
|
||||
|
||||
@@ -39,11 +39,14 @@ class SimpleMigrationServer {
|
||||
this.app.get("/", (req, res) => {
|
||||
res.set("Content-Type", "text/html");
|
||||
|
||||
// HTML meta tag redirect to /status
|
||||
// Don't use meta tag redirect, it may cause issues in Chrome (#6223)
|
||||
res.end(`
|
||||
<html lang="en">
|
||||
<head><meta http-equiv="refresh" content="0; URL=/migrate-status" /></head>
|
||||
<body>Migration server is running.</body>
|
||||
<head><title>Uptime Kuma Migration</title></head>
|
||||
<body>
|
||||
Migration is in progress, it may take some time. You can check the progress in the console, or
|
||||
<a href="/migrate-status" target="_blank">click here to check</a>.
|
||||
</body>
|
||||
</html>
|
||||
`);
|
||||
});
|
||||
|
||||
@@ -11,6 +11,14 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="google-chat-max-retries" class="form-label">{{ $t("Maximum Retries") }}<span style="color: red;"><sup>*</sup></span></label>
|
||||
<input id="google-chat-max-retries" v-model.number="$parent.notification.googleChatMaxRetries" type="number" class="form-control" min="1" max="10" step="1" required>
|
||||
<div class="form-text">
|
||||
{{ $t("Number of retry attempts if webhook fails") }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<div class="form-check form-switch">
|
||||
<input id="google-chat-use-template" v-model="$parent.notification.googleChatUseTemplate" type="checkbox" class="form-check-input">
|
||||
@@ -45,5 +53,11 @@ export default {
|
||||
]);
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
// Initialize default if needed
|
||||
if (!this.$parent.notification.googleChatMaxRetries) {
|
||||
this.$parent.notification.googleChatMaxRetries ||= 1;
|
||||
}
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -12,6 +12,21 @@
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="webhook-http-method" class="form-label">{{ $t("HTTP Method") }}</label>
|
||||
<select
|
||||
id="webhook-http-method"
|
||||
v-model="$parent.notification.httpMethod"
|
||||
class="form-select"
|
||||
>
|
||||
<option value="post">POST</option>
|
||||
<option value="get">GET</option>
|
||||
</select>
|
||||
<div class="form-text">
|
||||
{{ $parent.notification.httpMethod === 'get' ? $t("webhookGetMethodDesc") : $t("webhookPostMethodDesc") }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="$parent.notification.httpMethod === 'post'" class="mb-3">
|
||||
<label for="webhook-request-body" class="form-label">{{ $t("Request Body") }}</label>
|
||||
<select
|
||||
id="webhook-request-body"
|
||||
@@ -82,6 +97,11 @@ export default {
|
||||
]);
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
if (typeof this.$parent.notification.httpMethod === "undefined") {
|
||||
this.$parent.notification.httpMethod = "post";
|
||||
}
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1209,5 +1209,18 @@
|
||||
"Send UP silently": "Безшумно известяване за Достъпен",
|
||||
"Send DOWN silently": "Безшумно известяване за Недостъпен",
|
||||
"Installing a Nextcloud Talk bot requires administrative access to the server.": "Инсталирането на Nextcloud Talk бот изисква администраторски достъп до сървъра.",
|
||||
"auto-select": "Автоматичен избор"
|
||||
"auto-select": "Автоматичен избор",
|
||||
"supportBaleChatID": "Поддръжка на директен чат / група / чат ID на канала",
|
||||
"wayToGetBaleChatID": "Можете да получите вашия чат ID, като изпратите съобщение до бота и отидете на този URL адрес, за да видите chat_id:",
|
||||
"wayToGetBaleToken": "Можете да получите токен код от {0}.",
|
||||
"Mention Mobile List": "Списък със споменаващи мобилни устройства",
|
||||
"Mention User List": "Списък със споменаващи потребителски ID-та",
|
||||
"Dingtalk Mobile List": "Списък с мобилни устройства",
|
||||
"Dingtalk User List": "Списък с потребителски ID-та",
|
||||
"Enter a list of userId": "Въведете списък с потребителски ID-та",
|
||||
"Enter a list of mobile": "Въведете списък с мобилни устройства",
|
||||
"Invalid mobile": "Невалиден мобилен телефон [{mobile}]",
|
||||
"Invalid userId": "Невалидно потребителско ID [{userId}]",
|
||||
"Maximum Retries": "Максимален брой повторни опити",
|
||||
"Number of retry attempts if webhook fails": "Брой повторни опити (на всеки 60-180 секунди), ако уеб куката се првали."
|
||||
}
|
||||
|
||||
@@ -225,5 +225,48 @@
|
||||
"ignoreTLSError": "HTTPS ওয়েবসাইটগুলির জন্য TLS/SSL ত্রুটিগুলি উপেক্ষা করুন",
|
||||
"pushViewCode": "পুশ মনিটর কীভাবে ব্যবহার করবেন? (কোড দেখুন)",
|
||||
"Appearance": "দেখানোর ধরন",
|
||||
"Quick Stats": "তাৎক্ষণিক পরিসংখ্যান"
|
||||
"Quick Stats": "তাৎক্ষণিক পরিসংখ্যান",
|
||||
"affectedMonitorsDescription": "বর্তমান রক্ষণাবেক্ষণে প্রভাবিত মনিটরগুলো নির্বাচন করুন",
|
||||
"atLeastOneMonitor": "অন্তত একটি প্রভাবিত মনিটর নির্বাচন করুন",
|
||||
"pushoversounds cashregister": "নগদ রেজিস্টার",
|
||||
"pushoversounds incoming": "আগত",
|
||||
"importHandleDescription": "আপনি যদি একই নামের প্রতিটি মনিটর অথবা নোটিফিকেশান এড়িয়ে যেতে চান তাহলে 'যেটা এখন আছে' সেটা এড়িয়ে যান । 'ওভাররাইট' প্রতিটি বিদ্যমান মনিটর এবং নোটিফিকেশান মুছে ফেলবে।",
|
||||
"confirmImportMsg": "তুমি কি নিশ্চিত যে তুমি ব্যাকআপটি ইমপোর্ট করতে চাও? অনুগ্রহ করে যাচাই করো তুমি সঠিক ইমপোর্ট অপশনটি নির্বাচন করেছ কিনা।",
|
||||
"twoFAVerifyLabel": "দয়া করে আপনার টোকেনটি প্রবেশ করুন ২-ধাপ যাচাইকরণের (2FA) জন্যঃ",
|
||||
"tokenValidSettingsMsg": "টোকেনটি বৈধ! এখন আপনি ২-ধাপ যাচাইকরণের (2FA) সেটিংস সংরক্ষণ করতে পারেন।",
|
||||
"confirmEnableTwoFAMsg": "তুমি কি নিশ্চিত যে তুমি ২-ধাপ যাচাইকরণ (2FA) চালু করতে চাও?",
|
||||
"confirmDisableTwoFAMsg": "তুমি কি নিশ্চিত যে তুমি ২-ধাপ যাচাইকরণ (2FA) বন্ধ করতে চাও?",
|
||||
"recurringIntervalMessage": "প্রতিদিন একবার চালান । প্রতি {0} দিনে একবার চালান",
|
||||
"affectedStatusPages": "নির্বাচিত স্ট্যাটাস পেজগুলোতে এই রক্ষণাবেক্ষণ বার্তাটি দেখান",
|
||||
"passwordNotMatchMsg": "পুনরায় দেওয়া পাসওয়ার্ডটি মিলছে না।",
|
||||
"invertKeywordDescription": "কীওয়ার্ডটি উপস্থিত আছে কি না নয়, বরং অনুপস্থিত আছে কি না দেখুন।",
|
||||
"jsonQueryDescription": "সার্ভারের JSON প্রতিক্রিয়া থেকে JSON কুয়েরি ব্যবহার করে নির্দিষ্ট ডেটা পার্স ও এক্সট্র্যাক্ট করুন; যদি JSON প্রত্যাশিত না হয়, তাহলে প্রতিক্রিয়ার জন্য “$” ব্যবহার করুন। প্রাপ্ত ফল এরপর প্রত্যাশিত মানের সাথে **স্ট্রিং** হিসেবে তুলনা করা হবে। ডকুমেন্টেশনের জন্য {0} দেখুন এবং কুয়েরি নিয়ে পরীক্ষা করতে {1} ব্যবহার করুন।",
|
||||
"backupDescription": "আপনি সব মনিটর ও নোটিফিকেশনকে একটি JSON ফাইলে ব্যাকআপ নিতে পারেন।",
|
||||
"backupDescription2": "নোট: ইতিহাস ও ইভেন্ট সম্পর্কিত তথ্য এতে অন্তর্ভুক্ত নয়।",
|
||||
"backupDescription3": "নোটিফিকেশন টোকেনের মতো সংবেদনশীল ডেটা এক্সপোর্ট ফাইলে অন্তর্ভুক্ত থাকে; অনুগ্রহ করে এক্সপোর্টটি নিরাপদভাবে সংরক্ষণ করুন।",
|
||||
"endpoint": "সংযোগ বিন্দু",
|
||||
"octopushAPIKey": "কন্ট্রোল প্যানেলে থাকা HTTP API ক্রেডেনশিয়াল থেকে “API key” সংগ্রহ করুন",
|
||||
"octopushLogin": "কন্ট্রোল প্যানেলে থাকা HTTP API ক্রেডেনশিয়াল থেকে “লগইন” তথ্য সংগ্রহ করুন",
|
||||
"promosmsLogin": "API লগইন নাম",
|
||||
"promosmsPassword": "API পাসওয়ার্ড",
|
||||
"pushoversounds pushover": "Pushover (ডিফল্ট)",
|
||||
"pushoversounds bike": "সাইকেল",
|
||||
"pushoversounds bugle": "বিউগল",
|
||||
"pushoversounds classical": "ক্লাসিক",
|
||||
"pushoversounds cosmic": "মহাজাগতিক",
|
||||
"pushoversounds falling": "পড়ে যাওয়া / পতনশীল",
|
||||
"pushoversounds gamelan": "গামেলান",
|
||||
"pushoversounds intermission": "বিরতি",
|
||||
"pushoversounds magic": "ম্যাজিক",
|
||||
"pushoversounds mechanical": "যান্ত্রিক",
|
||||
"pushoversounds pianobar": "পিয়ানো বার",
|
||||
"pushoversounds siren": "সাইরেন",
|
||||
"pushoversounds spacealarm": "স্পেস অ্যালার্ম",
|
||||
"pushoversounds tugboat": "টাগবোট",
|
||||
"pushoversounds alien": "এলিয়েন সতর্কতা (দীর্ঘ)",
|
||||
"pushoversounds climb": "আরোহণ (দীর্ঘ)",
|
||||
"pushoversounds persistent": "স্থায়ী (দীর্ঘ)",
|
||||
"pushoversounds echo": "Pushover ইকো (দীর্ঘ)",
|
||||
"notificationDescription": "নোটিফিকেশনগুলো কার্যকর হতে হলে সেগুলোকে কোনো মনিটরের সাথে যুক্ত করতে হবে।",
|
||||
"keywordDescription": "সাধারণ HTML বা JSON প্রতিক্রিয়ায় কীওয়ার্ড অনুসন্ধান করুন। এই অনুসন্ধানটি কেস-সেনসিটিভ।"
|
||||
}
|
||||
|
||||
@@ -1201,5 +1201,19 @@
|
||||
"Bot secret": "Bot secret",
|
||||
"Send UP silently": "UP odeslat tiše",
|
||||
"Send DOWN silently": "DOWN odeslat tiše",
|
||||
"Installing a Nextcloud Talk bot requires administrative access to the server.": "Instalace Nextcloud Talk bota vyžaduje administrátorský přístup k serveru."
|
||||
"Installing a Nextcloud Talk bot requires administrative access to the server.": "Instalace Nextcloud Talk bota vyžaduje administrátorský přístup k serveru.",
|
||||
"Maximum Retries": "Max. počet pokusů",
|
||||
"Number of retry attempts if webhook fails": "Počet pokusů o opakování (každých 60–180 sekund), pokud se webhook nepodaří odeslat.",
|
||||
"wayToGetBaleToken": "Token můžete získat na adrese {0}.",
|
||||
"wayToGetBaleChatID": "ID chatu získáte zasláním zprávy robotovi a přejdete na tuto URL adresu, na které se zobrazí chat_id:",
|
||||
"supportBaleChatID": "Podpora přímého chatu / skupiny / ID chatu kanálu",
|
||||
"Dingtalk Mobile List": "Seznam telefonních čísel",
|
||||
"Dingtalk User List": "Seznam ID uživatelů",
|
||||
"Mention Mobile List": "Seznam Mention telefonních čísel",
|
||||
"Mention User List": "Seznam ID uživatelů Mention",
|
||||
"Enter a list of userId": "Zadejte seznam userID",
|
||||
"Enter a list of mobile": "Zadejte seznam telefonních čísel",
|
||||
"Invalid mobile": "Neplatné telefonní číslo [{mobile}]",
|
||||
"Invalid userId": "Neplatné userID [{userId}]",
|
||||
"auto-select": "Automatický výběr"
|
||||
}
|
||||
|
||||
@@ -1206,5 +1206,22 @@
|
||||
"Send UP silently": "UP still senden",
|
||||
"Send DOWN silently": "DOWN still senden",
|
||||
"Installing a Nextcloud Talk bot requires administrative access to the server.": "Die Installation eines Nextcloud Talk-Bots erfordert Administratorrechte für den Server.",
|
||||
"auto-select": "Automatische Auswahl"
|
||||
"auto-select": "Automatische Auswahl",
|
||||
"Dingtalk User List": "Benutzer-ID-Liste",
|
||||
"Invalid userId": "Ungültige Benutzer-ID [{userId}]",
|
||||
"supportBaleChatID": "Support-Direkt-Chat / Gruppe / Chat-ID des Kanals",
|
||||
"wayToGetBaleChatID": "Du kannst deine Chat-ID erhalten, indem du dem Bot eine Nachricht sendest und dann diese URL aufrufst, um die chat_id anzusehen:",
|
||||
"wayToGetBaleToken": "Du kannst ein Token von {0} erhalten.",
|
||||
"Mention Mobile List": "Mobile Liste erwähnen",
|
||||
"Mention User List": "Benutzer-ID-Liste erwähnen",
|
||||
"Dingtalk Mobile List": "Liste der Mobilgeräte",
|
||||
"Enter a list of userId": "Gib eine Liste von Benutzer-IDs ein",
|
||||
"Enter a list of mobile": "Gib eine Liste von Mobilnummern ein",
|
||||
"Invalid mobile": "Ungültige Mobilnummer [{mobile}]",
|
||||
"Number of retry attempts if webhook fails": "Anzahl der Wiederholungsversuche (alle 60–180 Sekunden), wenn der Webhook fehlschlägt.",
|
||||
"Maximum Retries": "Maximale Wiederholungsversuche",
|
||||
"descriptionHelpText": "Wird auf dem internen Dashboard angezeigt. Markdown ist zulässig und wird vor der Anzeige bereinigt (Leerzeichen und Einrückungen bleiben erhalten).",
|
||||
"HTTP Method": "HTTP-Methode",
|
||||
"webhookPostMethodDesc": "POST eignet sich für die meisten modernen HTTP-Server.",
|
||||
"webhookGetMethodDesc": "GET sendet Daten als Abfrageparameter und erlaubt keine Konfiguration eines Bodys. Nützlich zum Auslösen von Uptime Kuma Push-Überwachungen."
|
||||
}
|
||||
|
||||
@@ -1209,5 +1209,22 @@
|
||||
"Installing a Nextcloud Talk bot requires administrative access to the server.": "Die Installation eines Nextcloud Talk-Bots erfordert Administratorrechte für den Server.",
|
||||
"Nextcloud host": "Nextcloud Host",
|
||||
"Send UP silently": "UP still senden",
|
||||
"auto-select": "Automatische Auswahl"
|
||||
"auto-select": "Automatische Auswahl",
|
||||
"supportBaleChatID": "Support-Direkt-Chat / Gruppe / Chat-ID des Kanals",
|
||||
"wayToGetBaleChatID": "Du kannst deine Chat-ID erhalten, indem du dem Bot eine Nachricht sendest und dann diese URL aufrufst, um die chat_id anzusehen:",
|
||||
"wayToGetBaleToken": "Du kannst ein Token von {0} erhalten.",
|
||||
"Mention Mobile List": "Mobile Liste erwähnen",
|
||||
"Mention User List": "Benutzer-ID-Liste erwähnen",
|
||||
"Dingtalk Mobile List": "Liste der Mobilgeräte",
|
||||
"Dingtalk User List": "Benutzer-ID-Liste",
|
||||
"Enter a list of userId": "Gib eine Liste von Benutzer-IDs ein",
|
||||
"Enter a list of mobile": "Gib eine Liste von Mobilnummern ein",
|
||||
"Invalid mobile": "Ungültige Mobilnummer [{mobile}]",
|
||||
"Invalid userId": "Ungültige Benutzer-ID [{userId}]",
|
||||
"Number of retry attempts if webhook fails": "Anzahl der Wiederholungsversuche (alle 60–180 Sekunden), wenn der Webhook fehlschlägt.",
|
||||
"Maximum Retries": "Maximale Wiederholungsversuche",
|
||||
"webhookPostMethodDesc": "POST eignet sich für die meisten modernen HTTP-Server.",
|
||||
"descriptionHelpText": "Wird auf dem internen Dashboard angezeigt. Markdown ist zulässig und wird vor der Anzeige bereinigt (Leerzeichen und Einrückungen bleiben erhalten).",
|
||||
"HTTP Method": "HTTP-Methode",
|
||||
"webhookGetMethodDesc": "GET sendet Daten als Abfrageparameter und erlaubt keine Konfiguration eines Bodys. Nützlich zum Auslösen von Uptime Kuma Push-Überwachungen."
|
||||
}
|
||||
|
||||
@@ -306,6 +306,7 @@
|
||||
"Show Tags": "Show Tags",
|
||||
"Hide Tags": "Hide Tags",
|
||||
"Description": "Description",
|
||||
"descriptionHelpText": "Shown on the internal dashboard. Markdown is allowed and sanitized (preserves spaces and indentation) before display.",
|
||||
"No monitors available.": "No monitors available.",
|
||||
"Add one": "Add one",
|
||||
"No Monitors": "No Monitors",
|
||||
@@ -925,6 +926,9 @@
|
||||
"noGroupMonitorMsg": "Not Available. Create a Group Monitor First.",
|
||||
"Close": "Close",
|
||||
"Request Body": "Request Body",
|
||||
"HTTP Method": "HTTP Method",
|
||||
"webhookPostMethodDesc": "POST is good for most modern HTTP servers.",
|
||||
"webhookGetMethodDesc": "GET sends data as query parameters and does not allow configuring a body. Useful for triggering Uptime Kuma Push monitors.",
|
||||
"wayToGetFlashDutyKey": "To integrate Uptime Kuma with Flashduty: Go to Channels > Select a channel > Integrations > Add a new integration, choose Uptime Kuma, and copy the Push URL.",
|
||||
"FlashDuty Severity": "Severity",
|
||||
"FlashDuty Push URL": "Push URL",
|
||||
@@ -1170,5 +1174,7 @@
|
||||
"Bot secret": "Bot secret",
|
||||
"Send UP silently": "Send UP silently",
|
||||
"Send DOWN silently": "Send DOWN silently",
|
||||
"Installing a Nextcloud Talk bot requires administrative access to the server.": "Installing a Nextcloud Talk bot requires administrative access to the server."
|
||||
"Installing a Nextcloud Talk bot requires administrative access to the server.": "Installing a Nextcloud Talk bot requires administrative access to the server.",
|
||||
"Number of retry attempts if webhook fails": "Number of retry attempts (every 60-180 seconds) if the webhook fails.",
|
||||
"Maximum Retries": "Maximum Retries"
|
||||
}
|
||||
|
||||
@@ -1165,8 +1165,51 @@
|
||||
"No monitors found": "No se encontraron monitores.",
|
||||
"Could not clear events": "No se pudieron limpiar {failed}/{total} eventos",
|
||||
"smseagleGroupV2": "ID(s) de grupo(s) de Guía Telefónica",
|
||||
"mqttWebsocketPathExplanation": "Ruta del WebSocker para MQTT sobre conexion WebSocker (ejemplo, /mqtt)",
|
||||
"mqttWebsocketPathExplanation": "Ruta del WebSocker para MQTT sobre conexión WebSocker (ejemplo, /mqtt)",
|
||||
"OAuth Audience": "Audiencia OAuth",
|
||||
"Template plain text instead of using cards": "Plantilla de texto plano en vez de utilizar tarjetas",
|
||||
"issueWithGoogleChatOnAndroidHelptext": "Esto también permite evitar errores anteriores como {issuetackerURL}"
|
||||
"issueWithGoogleChatOnAndroidHelptext": "Esto también permite evitar errores anteriores como {issuetackerURL}",
|
||||
"wayToGetBaleChatID": "Puedes obtener tu ID de chat enviando un mensaje al bot y yendo a esta URL para ver el chat_id:",
|
||||
"smseagleContactV2": "ID(s) de contacto(s) de Guía Telefónica",
|
||||
"smseagleMsgTtsAdvanced": "Llamada avanzada de texto a voz",
|
||||
"wayToWriteEvolutionRecipient": "El número de teléfono con el prefijo internacional, pero sin el signo más al principio ({0}), el ID de contacto ({1}) o el ID de grupo ({2}).",
|
||||
"brevoApiHelp": "Crea una clave API aquí: {0}",
|
||||
"brevoLeaveBlankForDefaultName": "deja en blanco para el nombre por defecto",
|
||||
"brevoLeaveBlankForDefaultSubject": "Deja en blanco para sujeto por defecto",
|
||||
"Send DOWN silently": "Envía DOWN silenciosamente",
|
||||
"Installing a Nextcloud Talk bot requires administrative access to the server.": "La instalación de un bot de Nextcloud Talk requiere acceso administrativo al servidor.",
|
||||
"Font Twemoji by Twitter licensed under": "Fuente Twemoji de Twitter con licencia",
|
||||
"Optional: The audience to request the JWT for": "Opcional: La audiencia para la que se solicitará el JWT",
|
||||
"brevoApiKey": "Clave API de Brevo",
|
||||
"supportBaleChatID": "Soporte de chat directo/grupo/ID de chat del canal",
|
||||
"wayToGetBaleToken": "Puedes obtener un token de {0}.",
|
||||
"Mention Mobile List": "Mencionar lista de móviles",
|
||||
"Mention User List": "Mencionar la lista de id de usuario",
|
||||
"Dingtalk Mobile List": "Lista de móviles",
|
||||
"Dingtalk User List": "Lista de ID de usuario",
|
||||
"Enter a list of userId": "Ingresa una lista de userId",
|
||||
"Enter a list of mobile": "Ingresa una lista de móviles",
|
||||
"Invalid mobile": "Móvil inválido [{mobile}]",
|
||||
"Invalid userId": "UserId inválido [{userId}]",
|
||||
"smseagleTtsModel": "ID del modelo de texto a voz",
|
||||
"ntfyPriorityHelptextPriorityHigherThanDown": "La prioridad regular debe ser mayor que la prioridad {0}. La prioridad {1} es mayor que la prioridad {0} {2}",
|
||||
"FlashDuty Push URL": "URL push",
|
||||
"brevoFromEmail": "Desde el correo electrónico",
|
||||
"brevoFromName": "De Nombre",
|
||||
"brevoToEmail": "Para el correo electrónico",
|
||||
"brevoCcEmail": "Correo electrónico CC",
|
||||
"brevoBccEmail": "Correo electrónico BCC",
|
||||
"brevoSeparateMultipleEmails": "Separa múltiples direcciones de correo con comas",
|
||||
"brevoSubject": "Sujeto",
|
||||
"Staged Tags for Batch Add": "Etiquetas preparadas para agregar por lotes",
|
||||
"Nextcloud host": "Servidor Nextcloud",
|
||||
"Conversation token": "Token de conversación",
|
||||
"Bot secret": "Secreto del Bot",
|
||||
"Send UP silently": "Envía UP silenciosamente",
|
||||
"wayToGetEvolutionUrlAndToken": "Puede obtener la URL de la API y el token ingresando al canal deseado desde {0}",
|
||||
"evolutionRecipient": "Número de teléfono / ID de contacto / ID de grupo",
|
||||
"evolutionInstanceName": "Nombre de instancia",
|
||||
"auto-select": "Selección automática",
|
||||
"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"
|
||||
}
|
||||
|
||||
@@ -1209,5 +1209,22 @@
|
||||
"Conversation token": "Jeton de conversation",
|
||||
"Send UP silently": "Envoyer un UP silencieusement",
|
||||
"Installing a Nextcloud Talk bot requires administrative access to the server.": "L’installation d’un bot Nextcloud Talk nécessite un accès administratif au serveur.",
|
||||
"auto-select": "Sélection automatique"
|
||||
"auto-select": "Sélection automatique",
|
||||
"Enter a list of userId": "Entrez une liste d’IDs utilisateur",
|
||||
"supportBaleChatID": "Prend en charge l’ID de chat direct / de groupe / de canal",
|
||||
"wayToGetBaleChatID": "Vous pouvez obtenir votre ID de chat en envoyant un message au bot, puis en accédant à cette URL pour voir le chat_id :",
|
||||
"wayToGetBaleToken": "Vous pouvez obtenir un jeton depuis {0}.",
|
||||
"Mention Mobile List": "Liste des mentions mobiles",
|
||||
"Mention User List": "Liste des IDs des utilisateurs à mentionner",
|
||||
"Dingtalk Mobile List": "Liste des mobiles",
|
||||
"Dingtalk User List": "Liste des IDs utilisateurs",
|
||||
"Enter a list of mobile": "Entrez une liste de numéros de téléphone",
|
||||
"Invalid mobile": "Numéro de téléphone invalide [{mobile}]",
|
||||
"Invalid userId": "ID utilisateur invalide [{userId}]",
|
||||
"Maximum Retries": "Nombre maximal de tentatives",
|
||||
"Number of retry attempts if webhook fails": "Nombre de tentatives de réessai (toutes les 60 à 180 secondes) en cas d’échec du webhook.",
|
||||
"HTTP Method": "Méthode HTTP",
|
||||
"webhookPostMethodDesc": "POST convient à la plupart des serveurs HTTP modernes.",
|
||||
"webhookGetMethodDesc": "GET envoie les données sous forme de paramètres de requête et ne permet pas de configurer un corps. Utile pour déclencher les sondes Push d’Uptime Kuma.",
|
||||
"descriptionHelpText": "Affiché sur le tableau de bord interne. Le Markdown est autorisé et assaini (les espaces et l’indentation sont conservés) avant l’affichage."
|
||||
}
|
||||
|
||||
@@ -1142,5 +1142,38 @@
|
||||
"Clear All Events": "Glan Gach Imeacht",
|
||||
"clearAllEventsMsg": "An bhfuil tú cinnte gur mian leat gach imeacht a scriosadh?",
|
||||
"Template plain text instead of using cards": "Téacs simplí teimpléid in ionad cártaí a úsáid",
|
||||
"issueWithGoogleChatOnAndroidHelptext": "Ligeann sé seo freisin dul timpeall ar fhabhtanna suas an sruth cosúil le {issuetackerURL}"
|
||||
"issueWithGoogleChatOnAndroidHelptext": "Ligeann sé seo freisin dul timpeall ar fhabhtanna suas an sruth cosúil le {issuetackerURL}",
|
||||
"supportBaleChatID": "Tacaíocht Comhrá Díreach / Grúpa / ID Comhrá an Chainéil",
|
||||
"wayToGetBaleChatID": "Is féidir leat d’ID comhrá a fháil trí theachtaireacht a sheoladh chuig an bot agus dul chuig an URL seo chun an chat_id a fheiceáil:",
|
||||
"Mention Mobile List": "Luaigh liosta soghluaiste",
|
||||
"Mention User List": "Luaigh liosta aitheantais úsáideora",
|
||||
"Invalid mobile": "Fón póca neamhbhailí [{mobile}]",
|
||||
"brevoApiKey": "Eochair API Brevo",
|
||||
"brevoLeaveBlankForDefaultName": "fág bán don ainm réamhshocraithe",
|
||||
"brevoToEmail": "Chuig Ríomhphost",
|
||||
"brevoSeparateMultipleEmails": "Deighil seoltaí ríomhphoist iolracha le camóga",
|
||||
"Installing a Nextcloud Talk bot requires administrative access to the server.": "Éilíonn suiteáil bot Nextcloud Talk rochtain riaracháin ar an bhfreastalaí.",
|
||||
"brevoApiHelp": "Cruthaigh eochair API anseo: {0}",
|
||||
"brevoFromEmail": "Ó Ríomhphost",
|
||||
"brevoFromName": "Ó Ainm",
|
||||
"Nextcloud host": "Óstach Nextcloud",
|
||||
"Conversation token": "Comhartha comhrá",
|
||||
"Bot secret": "Rún an bhota",
|
||||
"wayToGetBaleToken": "Is féidir leat comhartha a fháil ó {0}.",
|
||||
"Dingtalk Mobile List": "Liosta soghluaiste",
|
||||
"Dingtalk User List": "Liosta aitheantais úsáideora",
|
||||
"Enter a list of userId": "Cuir isteach liosta d'AitheantasÚsáideora",
|
||||
"Enter a list of mobile": "Cuir isteach liosta de shoghluaisteáin",
|
||||
"Invalid userId": "Aitheantas úsáideora neamhbhailí [{userId}]",
|
||||
"wayToWriteEvolutionRecipient": "An uimhir theileafóin leis an réimír idirnáisiúnta, ach gan an comhartha móide ag an tús ({0}), an ID Teagmhála ({1}) ná an ID Grúpa ({2}).",
|
||||
"wayToGetEvolutionUrlAndToken": "Is féidir leat URL an API agus an comhartha a fháil trí dhul isteach sa chainéal atá uait ó {0}",
|
||||
"evolutionRecipient": "Uimhir Theileafóin / Aitheantas Teagmhála / Aitheantas Grúpa",
|
||||
"evolutionInstanceName": "Ainm an Cháis",
|
||||
"brevoCcEmail": "Ríomhphost CC",
|
||||
"brevoBccEmail": "Ríomhphost BCC",
|
||||
"brevoSubject": "Ábhar",
|
||||
"brevoLeaveBlankForDefaultSubject": "fág bán don ábhar réamhshocraithe",
|
||||
"Send UP silently": "Seol SUAS go ciúin",
|
||||
"Send DOWN silently": "Seol SÍOS go ciúin",
|
||||
"auto-select": "Roghnaigh Uathoibríoch"
|
||||
}
|
||||
|
||||
@@ -66,7 +66,7 @@
|
||||
"Keyword": "Ključna riječ",
|
||||
"Friendly Name": "Prilagođen naziv",
|
||||
"URL": "URL",
|
||||
"Hostname": "Domaćin",
|
||||
"Hostname": "Naziv hosta",
|
||||
"Port": "Port",
|
||||
"Heartbeat Interval": "Interval provjere",
|
||||
"Retries": "Broj ponovnih pokušaja",
|
||||
@@ -547,16 +547,16 @@
|
||||
"Query": "Upit",
|
||||
"settingsCertificateExpiry": "TLS istek certifikata",
|
||||
"certificationExpiryDescription": "HTTPS monitori će obavijesiti kada je istek TLS certifikata za:",
|
||||
"Setup Docker Host": "Dodaj Docker domaćina",
|
||||
"Setup Docker Host": "Dodaj Docker hosta",
|
||||
"Connection Type": "Tip veze",
|
||||
"Docker Daemon": "Docker daemon",
|
||||
"deleteDockerHostMsg": "Sigurno želite izbrisati ovog Docker domaćina za sve monitore?",
|
||||
"deleteDockerHostMsg": "Sigurno želite izbrisati ovog Docker hosta za sve monitore?",
|
||||
"socket": "Docker socket",
|
||||
"tcp": "TCP / HTTP",
|
||||
"Docker Container": "Docker kontejner",
|
||||
"Container Name / ID": "Naziv / identifikator kontejnera",
|
||||
"Docker Host": "Docker domaćin",
|
||||
"Docker Hosts": "Docker domaćini",
|
||||
"Docker Host": "Docker host",
|
||||
"Docker Hosts": "Docker hostovi",
|
||||
"ntfy Topic": "ntfy tema",
|
||||
"Domain": "Domena",
|
||||
"Workstation": "Radna stanica",
|
||||
@@ -885,15 +885,15 @@
|
||||
"templateHeartbeatJSON": "predmet koji opisuje provjeru",
|
||||
"templateMonitorJSON": "objekt koji opisuje Monitor",
|
||||
"templateLimitedToUpDownNotifications": "dostupno samo za obavijesti dostupnosti",
|
||||
"noDockerHostMsg": "Nije dostupno. Morate postaviti Docker domaćina.",
|
||||
"DockerHostRequired": "Postavite Docker domaćina za ovaj Monitor.",
|
||||
"noDockerHostMsg": "Nije dostupno. Morate postaviti Docker hosta.",
|
||||
"DockerHostRequired": "Postavite Docker hosta za ovaj Monitor.",
|
||||
"Browser Screenshot": "Snimka zaslona preglednika",
|
||||
"successKeyword": "Ključna riječ za uspjeh",
|
||||
"successKeywordExplanation": "MQTT ključna riječ koja označava uspješan odgovor",
|
||||
"remoteBrowserToggle": "Chromium se pokreće unutar Uptime Kuma kontejnera. Možete koristiti udaljeni preglednik uključivanjem ove opcije.",
|
||||
"deleteRemoteBrowserMessage": "Jeste li sigurni da želite ukloniti ovaj udaljeni preglednik za sve Monitore?",
|
||||
"Remote Browsers": "Udaljeni preglednici",
|
||||
"settingUpDatabaseMSG": "Postavljanje baze podatak u tijeku. Ovaj postupak može potrajati, budite strpljivi.",
|
||||
"settingUpDatabaseMSG": "Postavljanje baze podataka u tijeku. Ovaj postupak može potrajati, hvala na strpljenju.",
|
||||
"ntfyPriorityHelptextAllEvents": "Svi događaji šalju se s maksimalnim prioritetom",
|
||||
"ntfyPriorityHelptextAllExceptDown": "Svi događaji šalju se ovim prioritetom, osim {0} događaja, koji imaju prioritet {1}",
|
||||
"Search monitored sites": "Pretraži monitorirane stranice",
|
||||
@@ -947,7 +947,7 @@
|
||||
"wayToGetBitrix24Webhook": "Možete napraviti webhook prateći upute na {0}",
|
||||
"bitrix24SupportUserID": "Unesite svoj korisnički identifikator u Bitrix24. Možete ga dobiti iz URL-a odlaskom na vlastiti profil.",
|
||||
"apiKeySevenIO": "SevenIO API ključ",
|
||||
"Host URL": "URL domaćina",
|
||||
"Host URL": "URL hosta",
|
||||
"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": "Unesite adresu poslužitelja na koju se želite spojiti ili {localhost} ako planirate koristiti {local_mta}",
|
||||
"Select message type": "Odaberite tip poruke",
|
||||
"postToExistingThread": "Pošalji u postojeću temu / forumsku raspravu",
|
||||
@@ -1026,7 +1026,7 @@
|
||||
"Private Number": "Privatni broj",
|
||||
"wayToGetOnesenderUrlandToken": "URL i token možete dobiti odlaskom na OneSender web stranicu. Više informacija na {0}",
|
||||
"Token Onesender": "OneSender Token",
|
||||
"Host Onesender": "Adresa OneSender domaćina",
|
||||
"Host Onesender": "Adresa OneSender hosta",
|
||||
"Group ID": "Identifikator Grupe",
|
||||
"groupOnesenderDesc": "Provjerite je li Identifikator Grupe valjan. Za slanje poruke na Grupu, npr. 628123456789-342345",
|
||||
"Add Remote Browser": "Dodaj udaljeni preglednik",
|
||||
@@ -1098,7 +1098,7 @@
|
||||
"telegramTemplateFormatDescription": "Telegram dozvoljava korištenje različitih markup jezika za formatiranje poruka, pogledajte {0} za više detalja.",
|
||||
"YZJ Robot Token": "YZJ token robota",
|
||||
"YZJ Webhook URL": "YZJ URL webhooka",
|
||||
"templateHostnameOrURL": "domaćin ili URL",
|
||||
"templateHostnameOrURL": "naziv hosta ili URL",
|
||||
"templateStatus": "status",
|
||||
"telegramUseTemplateDescription": "Ako je omogućeno, poruka će biti poslana koristeći prilagođeni predložak.",
|
||||
"Plain Text": "Obični tekst",
|
||||
@@ -1157,7 +1157,7 @@
|
||||
"pingCountLabel": "Maks. paketa",
|
||||
"pingCountDescription": "Ukupan broj paketa koji će se poslati",
|
||||
"pingNumericLabel": "Brojčani ispis",
|
||||
"pingNumericDescription": "Ako je odabrano, bit će ispisane IP adrese umjesto naziva domaćina",
|
||||
"pingNumericDescription": "Ako je odabrano, bit će ispisane IP adrese umjesto naziva hostova",
|
||||
"pingGlobalTimeoutLabel": "Globalno vrijeme čekanja",
|
||||
"pingPerRequestTimeoutLabel": "Vrijeme čekanja za jedan ping",
|
||||
"pingIntervalAdjustedInfo": "Interval koji se prilagođava broju paketa, globalnom vremenu čekanja i vremenu čekanja jednog pinga",
|
||||
@@ -1196,5 +1196,29 @@
|
||||
"brevoSubject": "Predmet e-pošte",
|
||||
"brevoApiKey": "Brevo API ključ",
|
||||
"brevoLeaveBlankForDefaultName": "ostaviti prazno za zadani naziv",
|
||||
"brevoLeaveBlankForDefaultSubject": "ostaviti prazno za korištenje zadang teksta predmeta"
|
||||
"brevoLeaveBlankForDefaultSubject": "ostaviti prazno za korištenje zadang teksta predmeta",
|
||||
"Nextcloud host": "Nextcloud ime hosta",
|
||||
"Conversation token": "Token razgovora",
|
||||
"Bot secret": "Tajna bota",
|
||||
"Send UP silently": "Pošalji UP utišano",
|
||||
"auto-select": "Automatski odabir",
|
||||
"Send DOWN silently": "Pošalji DOWN utišano",
|
||||
"Installing a Nextcloud Talk bot requires administrative access to the server.": "Instaliranje Nextcloud Talk bota zahtijeva administratorski pristup poslužitelju.",
|
||||
"Number of retry attempts if webhook fails": "Broj ponovnih pokušaja (svakih 60-180 sekundi) ako webhook ne uspije.",
|
||||
"Maximum Retries": "Maksimalan broj ponovnih pokušaja",
|
||||
"HTTP Method": "HTTP metoda",
|
||||
"webhookPostMethodDesc": "POST je dobar za većinu modernih HTTP poslužitelja.",
|
||||
"Invalid userId": "Nevažeći korisnički ID [{userId}]",
|
||||
"supportBaleChatID": "Podržan ID izravnoga razgovora / grupe / kanala",
|
||||
"wayToGetBaleChatID": "Svoj ID razgovora (chat_id) možete dobiti slanjem poruke botu i odlaskom na ovaj URL:",
|
||||
"webhookGetMethodDesc": "GET šalje podatke kao parametre upita i ne dopušta konfiguriranje tijela zahtjeva. Korisno za okidanje Uptime Kuma Push monitora.",
|
||||
"descriptionHelpText": "Prikazuje se na internoj nadzornoj ploči. Markdown je dopušten i sanitiziran (čuva razmake i uvlačenja) prije prikazivanja.",
|
||||
"wayToGetBaleToken": "Token možete dobiti na {0}.",
|
||||
"Mention Mobile List": "Popis mobilnih uređaja za Mention",
|
||||
"Mention User List": "Popis korisničkih ID-jeva za Mention",
|
||||
"Dingtalk Mobile List": "Popis mobilnih uređaja",
|
||||
"Dingtalk User List": "Popis korisničkih ID-jeva",
|
||||
"Enter a list of userId": "Unesite popis korisničkih ID-jeva",
|
||||
"Enter a list of mobile": "Unesite popis mobilnih uređaja",
|
||||
"Invalid mobile": "Nevažeći mobitel [{mobile}]"
|
||||
}
|
||||
|
||||
@@ -1188,5 +1188,18 @@
|
||||
"Nextcloud host": "Sistema Nextcloud",
|
||||
"Conversation token": "Token di conversazione",
|
||||
"Installing a Nextcloud Talk bot requires administrative access to the server.": "Per installare un bot Nextcloud Talk è necessario disporre dell'accesso amministrativo al server.",
|
||||
"auto-select": "Selezione automatica"
|
||||
"auto-select": "Selezione automatica",
|
||||
"supportBaleChatID": "Supporto Chat Diretta / Gruppo / ID Chat del Canale",
|
||||
"wayToGetBaleChatID": "Puoi ottenere il tuo ID chat inviando un messaggio al bot e andando a questo URL per visualizzare la chat_id:",
|
||||
"wayToGetBaleToken": "Puoi ottenere un token da {0}.",
|
||||
"Mention Mobile List": "Menziona l'elenco dei cellulari",
|
||||
"Mention User List": "Menziona l'elenco degli ID utente",
|
||||
"Dingtalk Mobile List": "Elenco dei cellulari",
|
||||
"Dingtalk User List": "Elenco ID utente",
|
||||
"Enter a list of userId": "Inserisci un elenco di userId",
|
||||
"Invalid mobile": "Cellulare non valido [{mobile}]",
|
||||
"Enter a list of mobile": "Inserisci un elenco di cellulari",
|
||||
"Invalid userId": "ID utente non valido [{userId}]",
|
||||
"Maximum Retries": "Tentativi massimi",
|
||||
"Number of retry attempts if webhook fails": "Numero di tentativi di ripetizione (ogni 60-180 secondi) se il webhook fallisce."
|
||||
}
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
{
|
||||
"Dashboard": "დაფა",
|
||||
"Dashboard": "საინფორმაციო დაფა",
|
||||
"Help": "დახმარება",
|
||||
"New Update": "განახლება",
|
||||
"New Update": "ახალი განახლება",
|
||||
"Language": "ენა",
|
||||
"Appearance": "ვიზუალი",
|
||||
"Theme": "სტილი",
|
||||
"Game": "თამაში",
|
||||
"Version": "ვერსია",
|
||||
"Quick Stats": "თვალის გადავლება",
|
||||
"Quick Stats": "მოკლე სტატისტიკა",
|
||||
"Up": "მაღლა",
|
||||
"Pending": "მოლოდინი",
|
||||
"languageName": "Georgian",
|
||||
"Settings": "კონფიგურაცია",
|
||||
"languageName": "ქართული",
|
||||
"Settings": "პარამეტრები",
|
||||
"General": "ძირითადი",
|
||||
"Check Update On GitHub": "GitHub_ზე განახლების შემოწმება",
|
||||
"Check Update On GitHub": "GitHub ზე განახლების შემოწმება",
|
||||
"List": "სია",
|
||||
"Add": "დამატება",
|
||||
"Add New Monitor": "ახალი მონიტორის დამატება",
|
||||
@@ -39,5 +39,41 @@
|
||||
"Keyword": "საკვანძო სიტყვა",
|
||||
"Unknown": "უცნობი",
|
||||
"dbName": "მონაცემთა ბაზის სახელი",
|
||||
"Home": "მთავარი"
|
||||
"Home": "მთავარი",
|
||||
"Setup Notification": "შეტყობინების გამართვა",
|
||||
"Notification Type": "შეტყობინების ტიპი",
|
||||
"Schedule maintenance": "ტექნიკური მომსახურების დაგეგმვა",
|
||||
"Notifications": "შეტყობინებები",
|
||||
"Search Engine Visibility": "საძიებო სისტემებში ხილვადობა",
|
||||
"Discourage search engines from indexing site": "საძიებო სისტემებისთვის საიტის ინდექსირების აკრძალვა",
|
||||
"Entry Page": "საწყისი გვერდი",
|
||||
"Monitor History": "მონიტორის ისტორია",
|
||||
"telegramSendSilentlyDescription": "შეტყობინებას ჩუმად აგზავნის. მომხმარებლი შეტყობინებას ხმის გარეშე მიიღებს.",
|
||||
"telegramProtectContentDescription": "ჩართვის შემთხვევაში, Telegram-ში ბოტის შეტყობინებები დაცული იქნება გადამისამართებისა და შენახვისგან.",
|
||||
"Body Encoding": "ტანის ენკოდირება",
|
||||
"Expires": "ვადა იწურება",
|
||||
"Auto Get": "ავტომატური შევსება",
|
||||
"Display Timezone": "საჩვენებელი დროის სარტყელი",
|
||||
"notificationRegional": "რეგიონალური",
|
||||
"Clone Monitor": "მონიტორის კლონირება",
|
||||
"Clone": "კლონირება",
|
||||
"cloneOf": "დაკლონე {0}",
|
||||
"API Keys": "API გასაღებები",
|
||||
"Add API Key": "API გასაღების დამატება",
|
||||
"supportTelegramChatID": "პირდაპირი ჩატის / ჯგუფის / არხის ჩატის ID-ის მხარდაჭერა",
|
||||
"Powered by": "მხარდაჭერილია",
|
||||
"Proxies": "პროქსიები",
|
||||
"Reverse Proxy": "საპირისპირო პროქსი",
|
||||
"Bot Token": "ბოტის ტოკენი",
|
||||
"Chat ID": "ჩათის ID",
|
||||
"telegramMessageThreadID": "(ნებაყოფლობითი) შეტყობინების ნაკადის ID",
|
||||
"telegramProtectContent": "გადამისამართებისგან/შენახვისგან დაცვა",
|
||||
"Save": "შენახვა",
|
||||
"setupDatabaseMariaDB": "გარე MariaDB მონაცემთა ბაზასთან დაკავშირება. საჭიროა შეავსო მონაცემთა ბაზასთან დასაკავშირებელი ინფორმაცია.",
|
||||
"settingUpDatabaseMSG": "მიმდინარეობს მონაცემთა ბაზის დაყენება. შესაძლოა გარკვეული დრო დასჭირდეს, გთხოვთ, მოთმინება გამოიჩინოთ.",
|
||||
"Cannot connect to the socket server": "socket სერვერთან დაკავშირება შეუძლებელია",
|
||||
"Reconnecting...": "მიმდინარეობს დაკავშირება...",
|
||||
"Primary Base URL": "ძირითადი ძირი URL",
|
||||
"Maintenance": "მოვლა",
|
||||
"statusMaintenance": "მუშავდება"
|
||||
}
|
||||
|
||||
@@ -863,7 +863,7 @@
|
||||
"emailTemplateHeartbeatJSON": "하트 비트를 설명하는 객체",
|
||||
"pushoverMessageTtl": "메시지 TTL(초)",
|
||||
"Bark API Version": "Bark API 버전",
|
||||
"Mentioning": "멘토링",
|
||||
"Mentioning": "멘션하기",
|
||||
"Mention group": "{group}을(를) 멘션",
|
||||
"setup a new monitor group": "새 모니터 그룹 설정",
|
||||
"openModalTo": "{0}을(를) 위한 모달 열기",
|
||||
@@ -924,5 +924,41 @@
|
||||
"Badge URL": "배지 URL",
|
||||
"Group": "그룹",
|
||||
"monitorToastMessagesLabel": "모니터 토스트 알림",
|
||||
"monitorToastMessagesDescription": "모니터용 토스트 알림은 지정된 초 단위 시간이 지나면 사라집니다. -1로 설정하면 시간 초과를 비활성화합니다. 0으로 설정하면 토스트 알림을 비활성화합니다."
|
||||
"monitorToastMessagesDescription": "모니터용 토스트 알림은 지정된 초 단위 시간이 지나면 사라집니다. -1로 설정하면 시간 초과를 비활성화합니다. 0으로 설정하면 토스트 알림을 비활성화합니다.",
|
||||
"supportBaleChatID": "개인 채팅 / 그룹 / 채널의 ID를 지원해요",
|
||||
"wayToGetBaleChatID": "봇에 메시지를 보내 채팅 ID를 얻고 밑에 URL로 이동해 chat_id를 볼 수 있어요:",
|
||||
"wayToGetBaleToken": "토큰은 여기서 얻을 수 있어요: {0}.",
|
||||
"auto-select": "자동 선택",
|
||||
"issueWithGoogleChatOnAndroidHelptext": "이를 통해 {issuetackerURL}과 같은 상류 버그를 우회할 수도 있습니다",
|
||||
"wayToGetHeiiOnCallDetails": "트리거 ID와 API 키를 얻는 방법은 {documentation}에 설명되어 있습니다",
|
||||
"authIncorrectCreds": "사용자명 혹은 비밀번호가 올바르지 않습니다.",
|
||||
"successDeleted": "삭제되었습니다.",
|
||||
"foundChromiumVersion": "Chromium/Chrome 확인됨. 버전 {0}",
|
||||
"2faAlreadyEnabled": "2FA가 이미 활성화되어 있습니다.",
|
||||
"authInvalidToken": "유효하지 않은 토큰입니다.",
|
||||
"Close": "닫기",
|
||||
"Dingtalk User List": "사용자 ID 목록",
|
||||
"Enter a list of userId": "userId 목록을 입력하세요",
|
||||
"Enter a list of mobile": "전화번호 목록을 입력하세요",
|
||||
"Invalid userId": "유효하지 않은 userId: [{userId}]",
|
||||
"Mention User List": "사용자 ID 목록을 멘션",
|
||||
"Invalid mobile": "유효하지 않은 전화번호: [{mobile}]",
|
||||
"Dingtalk Mobile List": "전화번호 목록",
|
||||
"Mention Mobile List": "전화번호 목록을 멘션",
|
||||
"FlashDuty Push URL": "푸시 URL",
|
||||
"Bitrix24 Webhook URL": "Bitrix24 Webhook URL",
|
||||
"wayToGetSevenIOApiKey": "app.seven.io > 개발자 > API 키 > 녹색 추가 버튼 아래 대시보드를 방문하세요",
|
||||
"2faEnabled": "2FA가 활성화되었습니다.",
|
||||
"2faDisabled": "2FA가 비활성화되었습니다.",
|
||||
"wayToGetBitrix24Webhook": "{0}의 단계를 따라 webhook을 생성할 수 있습니다",
|
||||
"successResumed": "성공적으로 재개되었습니다.",
|
||||
"successPaused": "성공적으로 일시 중지되었습니다.",
|
||||
"successEdited": "수정되었습니다.",
|
||||
"successAdded": "추가되었습니다.",
|
||||
"successAuthChangePassword": "비밀번호가 성공적으로 업데이트되었습니다.",
|
||||
"Telephone number": "전화번호",
|
||||
"SNMP Version": "SNMP 버전",
|
||||
"Originator": "발신자",
|
||||
"cellsyntOriginator": "수신자의 휴대폰에 메시지 발신자로 표시됩니다. 허용되는 값과 기능은 originatortype 매개변수에 따라 다릅니다.",
|
||||
"Message Template": "메세지 템플릿"
|
||||
}
|
||||
|
||||
106
src/lang/pl.json
106
src/lang/pl.json
@@ -369,8 +369,8 @@
|
||||
"smseagleRecipient": "Odbiorca/y (wiele musi być oddzielone przecinkami)",
|
||||
"smseagleToken": "Klucz dostępu API",
|
||||
"smseagleUrl": "URL Twojego urządzenia SMSEagle",
|
||||
"smseagleEncoding": "Wyślij jako Unicode",
|
||||
"smseaglePriority": "Priorytet wiadomości (0-9, domyślnie = 0)",
|
||||
"smseagleEncoding": "Wyślij jako Unicode (domyślnie=GSM-7)",
|
||||
"smseaglePriority": "Priorytet wiadomości (0-9, najwyższy priorytet = 9)",
|
||||
"stackfield": "Stackfield",
|
||||
"Customize": "Dostosuj",
|
||||
"Custom Footer": "Niestandardowa stopka",
|
||||
@@ -799,7 +799,7 @@
|
||||
"showCertificateExpiry": "Pokaż wygaśnięcie certyfikatu",
|
||||
"gamedigGuessPortDescription": "Port używany przez Valve Server Query Protocol może różnić się od portu klienta. Spróbuj tego, jeśli monitor nie może połączyć się z serwerem.",
|
||||
"Secret AccessKey": "Tajny klucz AccessKey",
|
||||
"wayToGetFlashDutyKey": "Możesz przejść do Channel -> (Select a Channel) -> Integrations -> Add a new integration' page, dodać \"Uptime Kuma\", aby uzyskać adres push, skopiować klucz integracji w adresie. Więcej informacji można znaleźć na stronie",
|
||||
"wayToGetFlashDutyKey": "Aby zintegrować Uptime Kuma z Flashduty: Przejdź do Kanały > Wybierz kanał > Integracje > Dodaj nową integrację, wybierz Uptime Kuma i skopiuj adres URL Push.",
|
||||
"Badge Down Color": "Kolor odznaki Offline",
|
||||
"Notify Channel": "Powiadom kanał",
|
||||
"Request Timeout": "Limit czasu żądania",
|
||||
@@ -1087,7 +1087,7 @@
|
||||
"Scifi": "Scifi",
|
||||
"Clear": "Clear",
|
||||
"Elevator": "Elevator",
|
||||
"Guitar": "Guitar",
|
||||
"Guitar": "Gitara",
|
||||
"Pop": "Pop",
|
||||
"RabbitMQ Nodes": "Węzły zarządzania RabbitMQ",
|
||||
"rabbitmqNodesDescription": "Wprowadź adres URL węzłów zarządzania RabbitMQ, w tym protokół i port. Przykład: {0}",
|
||||
@@ -1124,5 +1124,101 @@
|
||||
"the smsplanet documentation": "dokumentacja smsplanet",
|
||||
"Phone numbers": "Numery telefonów",
|
||||
"Sender name": "Nazwa nadawcy",
|
||||
"smsplanetNeedToApproveName": "Wymaga zatwierdzenia w panelu klienta"
|
||||
"smsplanetNeedToApproveName": "Wymaga zatwierdzenia w panelu klienta",
|
||||
"Staged Tags for Batch Add": "Tagi etapowe dla dodawania zbiorczego",
|
||||
"Happy Eyeballs algorithm": "Algorytm Happy Eyeballs",
|
||||
"smseagleApiv1": "APIv1 (dla istniejących projektów i kompatybilności wstecznej)",
|
||||
"ntfyPriorityDown": "Priorytet dla zdarzeń DOWN",
|
||||
"mqttWebsocketPathExplanation": "Ścieżka WebSocket dla połączeń MQTT przez WebSocket (np. /mqtt)",
|
||||
"supportBaleChatID": "Pomoc techniczna Czat bezpośredni / Grupa / Identyfikator czatu kanału",
|
||||
"wayToGetBaleChatID": "Możesz uzyskać swój identyfikator czatu, wysyłając wiadomość do bota i przechodząc do tego adresu URL, aby wyświetlić identyfikator czatu:",
|
||||
"OAuth Audience": "Odbiorcy OAuth",
|
||||
"Ip Family": "Rodzina IP",
|
||||
"Conversation token": "Token konwersacji",
|
||||
"Installing a Nextcloud Talk bot requires administrative access to the server.": "Instalacja bota Nextcloud Talk wymaga uprawnień administratora serwera.",
|
||||
"Manual": "Instrukcja obsługi",
|
||||
"clearAllEventsMsg": "Czy na pewno chcesz usunąć wszystkie wydarzenia?",
|
||||
"Clear All Events": "Wyczyść wszystkie wydarzenia",
|
||||
"smseagleMsgType": "Typ wiadomości",
|
||||
"smseagleMsgSms": "Wiadomość SMS (domyślnie)",
|
||||
"smseagleMsgRing": "Dzwonek",
|
||||
"smseagleMsgTts": "Połączenie z funkcją zamiany tekstu na mowę",
|
||||
"smseagleMsgTtsAdvanced": "Zaawansowana funkcja zamiany tekstu na mowę",
|
||||
"smseagleTtsModel": "Identyfikator modelu zamiany tekstu na mowę",
|
||||
"smseagleApiType": "Wersja API",
|
||||
"smseagleApiv2": "APIv2 (zalecane dla nowych integracji)",
|
||||
"smseagleDocs": "Sprawdź dokumentację lub dostępność APIv2: {0}",
|
||||
"smseagleComma": "Wiele wartości należy oddzielić przecinkami",
|
||||
"pingCountLabel": "Maksymalna liczba pakietów",
|
||||
"pingGlobalTimeoutDescription": "Całkowity czas w sekundach przed zatrzymaniem pingowania, niezależnie od wysłanych pakietów",
|
||||
"pingPerRequestTimeoutLabel": "Limit czasu na ping",
|
||||
"pingPerRequestTimeoutDescription": "Jest to maksymalny czas oczekiwania (w sekundach) przed uznaniem pojedynczego pakietu ping za utracony",
|
||||
"Path": "Ścieżka",
|
||||
"mqttWebSocketPath": "Ścieżka MQTT WebSocket",
|
||||
"mqttWebsocketPathInvalid": "Użyj prawidłowego formatu ścieżki WebSocket",
|
||||
"mqttHostnameTip": "Użyj tego formatu {hostnameFormat}",
|
||||
"brevoApiKey": "Klucz API Brevo",
|
||||
"brevoApiHelp": "Utwórz klucz API tutaj: {0}",
|
||||
"brevoToEmail": "Na adres e-mail",
|
||||
"Clear Form": "Wyczyść formularz",
|
||||
"wayToGetBaleToken": "Możesz otrzymać token z {0}.",
|
||||
"Use HTML for custom E-mail body": "Użyj HTML do tworzenia niestandardowej treści wiadomości e-mail",
|
||||
"Events cleared successfully": "Wydarzenia zostały pomyślnie usunięte.",
|
||||
"No monitors found": "Nie znaleziono monitorów.",
|
||||
"Could not clear events": "Nie można usunąć {failed}/{total} zdarzeń",
|
||||
"Mention Mobile List": "Lista urządzeń mobilnych Mention",
|
||||
"Mention User List": "Lista identyfikatorów użytkowników Mention",
|
||||
"Dingtalk Mobile List": "Lista urządzeń mobilnych",
|
||||
"Dingtalk User List": "Lista identyfikatorów użytkowników",
|
||||
"Enter a list of userId": "Wprowadź listę identyfikatorów użytkowników",
|
||||
"Enter a list of mobile": "Wprowadź listę numerów telefonów komórkowych",
|
||||
"Invalid mobile": "Nieprawidłowy numer telefonu komórkowego [{mobile}]",
|
||||
"Invalid userId": "Nieprawidłowy identyfikator użytkownika [{userId}]",
|
||||
"smseagleContactV2": "Identyfikatory kontaktów w książce telefonicznej",
|
||||
"smseagleDuration": "Czas trwania (w sekundach)",
|
||||
"SpugPush Template Code": "Kod szablonu",
|
||||
"ntfyPriorityHelptextPriorityHigherThanDown": "Priorytet regularny powinien być wyższy niż priorytet {0}. Priorytet {1} jest wyższy niż priorytet {0} i priorytet {2}",
|
||||
"FlashDuty Push URL": "Adres URL Push",
|
||||
"FlashDuty Push URL Placeholder": "Kopiuj ze strony integracji alertów",
|
||||
"Optional: The audience to request the JWT for": "Opcjonalnie: odbiorca żądający JWT dla",
|
||||
"brevoFromEmail": "Z e-maila",
|
||||
"brevoFromName": "Od nazwy",
|
||||
"brevoLeaveBlankForDefaultName": "pozostaw puste pole, aby użyć nazwy domyślnej",
|
||||
"brevoCcEmail": "E-mail CC",
|
||||
"brevoBccEmail": "E-mail BCC",
|
||||
"brevoSeparateMultipleEmails": "Oddziel wiele adresów e-mail przecinkami",
|
||||
"brevoSubject": "Temat",
|
||||
"brevoLeaveBlankForDefaultSubject": "pozostaw puste pole dla domyślnego tematu",
|
||||
"pingCountDescription": "Liczba pakietów do wysłania przed zatrzymaniem",
|
||||
"pingNumericLabel": "Wyjście numeryczne",
|
||||
"pingNumericDescription": "Jeśli opcja jest zaznaczona, zamiast symbolicznych nazw hostów będą wyświetlane adresy IP",
|
||||
"pingGlobalTimeoutLabel": "Globalny limit czasu",
|
||||
"pingIntervalAdjustedInfo": "Interwał dostosowany na podstawie liczby pakietów, globalnego limitu czasu i limitu czasu na ping",
|
||||
"smtpHelpText": "„SMTPS” sprawdza, czy SMTP/TLS działa; „Ignore TLS” łączy się za pomocą zwykłego tekstu; „STARTTLS” łączy się, wysyła polecenie STARTTLS i weryfikuje certyfikat serwera. Żadna z tych opcji nie wysyła wiadomości e-mail.",
|
||||
"Custom URL": "Niestandardowy adres URL",
|
||||
"customUrlDescription": "Będzie używany jako klikalny adres URL zamiast adresu monitora.",
|
||||
"OneChatAccessToken": "Token dostępu OneChat",
|
||||
"OneChatUserIdOrGroupId": "Identyfikator użytkownika OneChat lub identyfikator grupy",
|
||||
"OneChatBotId": "Identyfikator bota OneChat",
|
||||
"Disable URL in Notification": "Wyłącz adres URL w powiadomieniu",
|
||||
"Add Another Tag": "Dodaj kolejny tag",
|
||||
"pause": "Pauza",
|
||||
"Nextcloud host": "Host Nextcloud",
|
||||
"Bot secret": "Sekret bota",
|
||||
"Send UP silently": "Wyślij UP po cichu",
|
||||
"Send DOWN silently": "Wyślij DOWN po cichu",
|
||||
"wayToWriteEvolutionRecipient": "Numer telefonu z prefiksem międzynarodowym, ale bez znaku plusa na początku ({0}), identyfikator kontaktu ({1}) lub identyfikator grupy ({2}).",
|
||||
"wayToGetEvolutionUrlAndToken": "Adres URL interfejsu API i token można uzyskać, przechodząc do wybranego kanału z {0}",
|
||||
"evolutionRecipient": "Numer telefonu / Identyfikator kontaktu / Identyfikator grupy",
|
||||
"evolutionInstanceName": "Nazwa instancji",
|
||||
"Template plain text instead of using cards": "Szablon zwykłego tekstu zamiast używania kart",
|
||||
"defaultFriendlyName": "Nowy monitor",
|
||||
"smseagleGroupV2": "Identyfikatory grup w książce telefonicznej",
|
||||
"Add Tags": "Dodaj tagi",
|
||||
"tagAlreadyOnMonitor": "Ten tag (nazwa i wartość) jest już na monitorze lub oczekuje na dodanie.",
|
||||
"tagAlreadyStaged": "Ten tag (nazwa i wartość) jest już przygotowany dla tej partii.",
|
||||
"tagNameExists": "Tag systemowy o tej nazwie już istnieje. Wybierz go z listy lub użyj innej nazwy.",
|
||||
"auto-select": "Wybór automatyczny",
|
||||
"issueWithGoogleChatOnAndroidHelptext": "Pozwala to również ominąć błędy upstream, takie jak {issuetackerURL}",
|
||||
"ipFamilyDescriptionAutoSelect": "Używa {happyEyeballs} do określania rodziny adresów IP."
|
||||
}
|
||||
|
||||
@@ -1173,5 +1173,16 @@
|
||||
"Send UP silently": "Enviar UP silenciosamente",
|
||||
"Send DOWN silently": "Envie DOWN silenciosamente",
|
||||
"Installing a Nextcloud Talk bot requires administrative access to the server.": "A instalação de um bot Nextcloud Talk requer acesso administrativo ao servidor.",
|
||||
"auto-select": "Seleção automática"
|
||||
"auto-select": "Seleção automática",
|
||||
"Mention Mobile List": "Mencionar lista de celulares",
|
||||
"Enter a list of userId": "Insira uma lista de userId",
|
||||
"supportBaleChatID": "Suporte a ID de Chat Direto / Grupo / Canal",
|
||||
"wayToGetBaleChatID": "Você pode obter seu ID de bate-papo enviando uma mensagem ao bot e acessando esta URL para visualizar o chat_id:",
|
||||
"wayToGetBaleToken": "Você pode obter um token de {0}.",
|
||||
"Mention User List": "Mencionar lista de IDs de usuários",
|
||||
"Dingtalk Mobile List": "Lista de celulares",
|
||||
"Dingtalk User List": "Lista de IDs de usuário",
|
||||
"Enter a list of mobile": "Insira uma lista de dispositivos móveis",
|
||||
"Invalid mobile": "Celular inválido [{mobile}]",
|
||||
"Invalid userId": "ID de usuário inválido [{userId}]"
|
||||
}
|
||||
|
||||
@@ -1216,5 +1216,6 @@
|
||||
"brevoLeaveBlankForDefaultSubject": "оставьте пустым для темы по умолчанию",
|
||||
"Nextcloud host": "Хост Nextcloud",
|
||||
"Installing a Nextcloud Talk bot requires administrative access to the server.": "Для установки бота Nextcloud Talk требуется административный доступ к серверу.",
|
||||
"Conversation token": "Токен разговора"
|
||||
"Conversation token": "Токен разговора",
|
||||
"auto-select": "Автоматический выбор"
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"Settings": "Nastavenia",
|
||||
"Help": "Nápoveda",
|
||||
"Help": "Pomoc",
|
||||
"New Update": "Nová aktualizácia",
|
||||
"Language": "Jazyk",
|
||||
"Appearance": "Vzhľad",
|
||||
@@ -18,8 +18,8 @@
|
||||
"General Monitor Type": "Všeobecný typ sledovania",
|
||||
"Passive Monitor Type": "Pasívny typ sledovania",
|
||||
"Specific Monitor Type": "Špecifický typ sledovania",
|
||||
"pauseDashboardHome": "Pauza",
|
||||
"Pause": "Pauza",
|
||||
"pauseDashboardHome": "Pozastavené",
|
||||
"Pause": "Pozastaviť",
|
||||
"Status": "Stav",
|
||||
"Message": "Správa",
|
||||
"No important events": "Žiadne dôležité udalosti",
|
||||
@@ -27,7 +27,7 @@
|
||||
"Delete": "Odstrániť",
|
||||
"Current": "Aktuálne",
|
||||
"Cert Exp.": "Platnosť cert.",
|
||||
"day": "deň | dni",
|
||||
"day": "deň | dní",
|
||||
"hour": "hodina",
|
||||
"Response": "Odpoveď",
|
||||
"Ping": "Ping",
|
||||
@@ -35,11 +35,11 @@
|
||||
"Friendly Name": "Názov",
|
||||
"Port": "Port",
|
||||
"Retries": "Opakovania",
|
||||
"Resend Notification if Down X times consecutively": "Poslať oznámenie znovu, ak je nedostupné X-krát za sebou",
|
||||
"Resend Notification if Down X times consecutively": "Znovu odoslať upozornenie, ak je X-krát po sebe nedostupné",
|
||||
"Advanced": "Pokročilé",
|
||||
"checkEverySecond": "Skontrolovať každých {0} sekúnd",
|
||||
"retryCheckEverySecond": "Zopakovať každých {0} sekúnd",
|
||||
"resendEveryXTimes": "Znovu poslať každých {0} krát",
|
||||
"resendEveryXTimes": "Odoslať znova po {0} pokusoch",
|
||||
"resendDisabled": "Opakované odoslanie vypnuté",
|
||||
"ignoreTLSError": "Ignorovať TLS/SSL chyby pre HTTPS stránky",
|
||||
"upsideDownModeDescription": "Obrátiť stav. Pokiaľ je služba dostupná, zobrazuje sa ako NEDOSTUPNÁ.",
|
||||
@@ -51,8 +51,8 @@
|
||||
"Notifications": "Notifikácie",
|
||||
"Not available, please setup.": "Nedostupné, prosím nastavte.",
|
||||
"Setup Notification": "Nastavenie notifikácií",
|
||||
"Dark": "Tmavý",
|
||||
"Light": "Svetlý",
|
||||
"Dark": "Tmavá",
|
||||
"Light": "Svetlá",
|
||||
"Auto": "Automaticky",
|
||||
"Normal": "Normálna",
|
||||
"Bottom": "Dole",
|
||||
@@ -81,7 +81,7 @@
|
||||
"maxRedirectDescription": "Maximálny počet presmerovaní, ktoré sa majú sledovať. Nastavte na 0, aby ste presmerovania deaktivovali.",
|
||||
"needPushEvery": "Tuto adresu by ste mali volať každých {0} sekúnd.",
|
||||
"pushOptionalParams": "Voliteľné parametre: {0}",
|
||||
"Theme - Heartbeat Bar": "Téma - Heartbeat Bar",
|
||||
"Theme - Heartbeat Bar": "Téma - lišta pulzu",
|
||||
"Game": "Hra",
|
||||
"Search Engine Visibility": "Viditeľnosť vyhľadávačmi",
|
||||
"Allow indexing": "Povoliť indexovanie",
|
||||
@@ -118,7 +118,7 @@
|
||||
"notAvailableShort": "N/A",
|
||||
"Default enabled": "Predvolene povolené",
|
||||
"Create": "Vytvoriť",
|
||||
"Clear Data": "Vyčistiť dáta",
|
||||
"Clear Data": "Vyčistiť údaje",
|
||||
"Events": "Udalosti",
|
||||
"Heartbeats": "Pulzy",
|
||||
"Auto Get": "Získať automaticky",
|
||||
@@ -142,12 +142,12 @@
|
||||
"Inactive": "Neaktívne",
|
||||
"Token": "Token",
|
||||
"Show URI": "Zobraziť URI",
|
||||
"Tags": "Značky",
|
||||
"Tags": "Štítky",
|
||||
"Add New below or Select...": "Pridať novú nižšie alebo vybrať…",
|
||||
"Tag with this value already exist.": "Značka s touto hodnotou už existuje.",
|
||||
"Tag with this value already exist.": "Štítok s touto hodnotou už existuje.",
|
||||
"color": "Farba",
|
||||
"value (optional)": "hodnota (voliteľné)",
|
||||
"Gray": "Šedá",
|
||||
"Gray": "Sivá",
|
||||
"Red": "Červená",
|
||||
"Orange": "Oranžová",
|
||||
"Green": "Zelená",
|
||||
@@ -176,7 +176,7 @@
|
||||
"webhookJsonDesc": "{0} je vhodný pre všetky moderné servery HTTP, ako napríklad Express.js",
|
||||
"webhookFormDataDesc": "{multipart} je dobré pre PHP. JSON bude potrebné analyzovať pomocou {decodeFunction}",
|
||||
"Generate": "Generovať",
|
||||
"Discourage search engines from indexing site": "Odradiť vyhľadávacie nástroje od indexovania stránky",
|
||||
"Discourage search engines from indexing site": "Zabrániť vyhľadávačom v indexovaní stránky",
|
||||
"disableauth.message1": "Ste si istý, že chcete {disableAuth}?",
|
||||
"disable authentication": "vypnúť autentifikáciu",
|
||||
"disableauth.message2": "Je navrhnutý pre scenáre, {intendThirdPartyAuth} pred Uptime Kuma, ako je Cloudflare Access, Authelia alebo iné autentifikačné mechanizmy.",
|
||||
@@ -189,8 +189,8 @@
|
||||
"Verify Token": "Overiť token",
|
||||
"Enable 2FA": "Povoliť 2FA",
|
||||
"Active": "Aktívne",
|
||||
"Add New Tag": "Pridať novú značku",
|
||||
"Tag with this name already exist.": "Značka s týmto názvom už existuje.",
|
||||
"Add New Tag": "Pridať nový štítok",
|
||||
"Tag with this name already exist.": "Štítok s týmto názvom už existuje.",
|
||||
"Blue": "Modrá",
|
||||
"Search...": "Hľadať…",
|
||||
"statusPageNothing": "Nič tu nie je, pridajte skupinu alebo sledovanie.",
|
||||
@@ -205,10 +205,10 @@
|
||||
"Read more": "Prečítajte si viac",
|
||||
"appriseInstalled": "Apprise je nainštalovaný.",
|
||||
"Reconnecting...": "Prepájanie...",
|
||||
"Request Timeout": "Platnosť požiadavky vypršala",
|
||||
"styleElapsedTimeShowWithLine": "Zobraziť (S Riadkom)",
|
||||
"Request Timeout": "Časový limit požiadavky",
|
||||
"styleElapsedTimeShowWithLine": "Zobraziť (s riadkom)",
|
||||
"webhookCustomBodyDesc": "Zadajte vlastné HTTP Body pre request. Šablónové premenné {msg}, {heartbeat}, {monitor} sú akceptované.",
|
||||
"timeoutAfter": "Platnosť požiadavky vypršala po {0} sekundách",
|
||||
"timeoutAfter": "Časový limit vyprší po {0} sekundách",
|
||||
"styleElapsedTime": "Uplynutý čas pod lištou pulzu",
|
||||
"styleElapsedTimeShowNoLine": "Zobraziť (Žiadny riadok)",
|
||||
"filterActive": "Aktívne",
|
||||
@@ -252,7 +252,7 @@
|
||||
"HTTP Headers": "HTTP hlavičky",
|
||||
"Other Software": "Iný softvér",
|
||||
"For example: nginx, Apache and Traefik.": "Napríklad: nginx, Apache a Traefik.",
|
||||
"Please read": "Prečítajte si, prosím",
|
||||
"Please read": "Prečítajte si prosím",
|
||||
"pushOthers": "Ostatné",
|
||||
"Created": "Vytvorené",
|
||||
"ignoreTLSErrorGeneral": "Ignorovať chybu TLS/SSL pre pripojenie",
|
||||
@@ -289,7 +289,7 @@
|
||||
"Security": "Zabezpečenie",
|
||||
"Steam API Key": "Kľúč API služby Steam",
|
||||
"Pick a RR-Type...": "Vyberte typ RR…",
|
||||
"Discard": "Vyhodiť",
|
||||
"Discard": "Zrušiť zmeny",
|
||||
"Select": "Vybrať",
|
||||
"selectedMonitorCount": "Vybrané: {0}",
|
||||
"Check/Uncheck": "Označiť/Odznačiť",
|
||||
@@ -352,14 +352,14 @@
|
||||
"deleteDockerHostMsg": "Ste si istí, že chcete odstrániť tohto docker hostiteľa pre všetky sledovania?",
|
||||
"Container Name / ID": "Názov kontajnera / ID",
|
||||
"telegramSendSilentlyDescription": "Odošle správu v tichosti. Používatelia dostanú oznámenie bez zvuku.",
|
||||
"trustProxyDescription": "Dôverujte hlavičkám 'X-Forwarded-*'. Ak chcete získať správnu IP adresu klienta a vaša služba Uptime Kuma sa nachádza za proxy serverom, napríklad Nginx alebo Apache, mali by ste túto funkciu povoliť.",
|
||||
"trustProxyDescription": "Dôverovať hlavičkám 'X-Forwarded-*'. Ak chcete získať správnu IP adresu klienta a vaša služba Uptime Kuma sa nachádza za proxy serverom, napríklad Nginx alebo Apache, mali by ste túto funkciu povoliť.",
|
||||
"Long-Lived Access Token can be created by clicking on your profile name (bottom left) and scrolling to the bottom then click Create Token. ": "Dlhodobý prístupový token môžete vytvoriť kliknutím na názov svojho profilu (vľavo dole) a rolovaním na spodok, potom kliknite na Vytvoriť token. ",
|
||||
"Event data:": "Dáta udalosti:",
|
||||
"Event data:": "Údaje udalosti:",
|
||||
"Then choose an action, for example switch the scene to where an RGB light is red.": "Potom vyberte akciu, napríklad prepnite scénu na miesto, kde je svetlo RGB červené.",
|
||||
"warningTimezone": "Používa časové pásmo servera",
|
||||
"lastDay1": "Posledný deň mesiaca",
|
||||
"smtpLiquidIntroduction": "Nasledujúce dve polia je možné šablónovať pomocou šablónovacieho jazyka Liquid. Pokyny na použitie nájdete v časti {0}. Toto sú dostupné premenné:",
|
||||
"wayToGetDiscordURL": "Získate to, ak prejdete do Nastavenia servera -> Integrácie -> Zobraziť webhooky -> Nový webhook",
|
||||
"wayToGetDiscordURL": "Toto nastavenie nájdete v časti Nastavenia servera -> Integrácie -> Zobraziť webhooky -> Nový webhook",
|
||||
"wayToGetLineChannelToken": "Najprv pristupte k {0}, vytvorte poskytovateľa a kanál ( Rozhranie API správ), potom môžete získať prístupový token kanála a ID používateľa z vyššie uvedených položiek ponuky.",
|
||||
"enableDefaultNotificationDescription": "Toto upozornenie bude predvolene povolené pre nové sledovania. Toto oznámenie môžete stále vypnúť pre každé sledovanie samostatne.",
|
||||
"or": "alebo",
|
||||
@@ -378,13 +378,13 @@
|
||||
"Issuer:": "Vydavateľ:",
|
||||
"Fingerprint:": "Odtlačok:",
|
||||
"No status pages": "Žiadne stavové stránky",
|
||||
"Domain Name Expiry Notification": "Oznámenie o vypršaní platnosti domény",
|
||||
"Domain Name Expiry Notification": "Oznámiť o vypršaní platnosti domény",
|
||||
"Remove the expiry notification": "Odstrániť deň oznámenia o vypršaní platnosti",
|
||||
"Proxy": "Proxy",
|
||||
"Date Created": "Dátum vytvorenia",
|
||||
"Footer Text": "Text päty",
|
||||
"Footer Text": "Text v pätičke",
|
||||
"RadiusCallingStationIdDescription": "Identifikátor volajúceho zariadenia",
|
||||
"Certificate Expiry Notification": "Oznámenie o skončení platnosti certifikátu",
|
||||
"Certificate Expiry Notification": "Oznámiť o skončení platnosti certifikátu",
|
||||
"API Username": "Používateľské meno API",
|
||||
"API Key": "Kľúč API",
|
||||
"Show update if available": "Zobraziť aktualizáciu, ak je k dispozícii",
|
||||
@@ -444,9 +444,9 @@
|
||||
"maintenanceStatus-unknown": "Neznáme",
|
||||
"IconUrl": "URL adresa ikony",
|
||||
"chromeExecutableAutoDetect": "Automatická detekcia",
|
||||
"chromeExecutableDescription": "Ak používatelia nástroja Docker ešte nemajú nainštalovanú aplikáciu Chromium, inštalácia a zobrazenie výsledkov testu môže trvať niekoľko minút. Zaberie 1 GB miesta na disku.",
|
||||
"chromeExecutableDescription": "Pre používateľov nástroja Docker, ak Chromium ešte nie je nainštalované, môže inštalácia a zobrazenie výsledku testu trvať niekoľko minút. Vyžaduje 1 GB miesta na disku.",
|
||||
"dnsCacheDescription": "V niektorých prostrediach IPv6 nemusí fungovať, ak narazíte na problémy, vypnite to.",
|
||||
"Single Maintenance Window": "Jediné okno údržby",
|
||||
"Single Maintenance Window": "Jedno okno údržby",
|
||||
"install": "Nainštalovať",
|
||||
"installing": "Inštaluje sa",
|
||||
"uninstall": "Odinštalovať",
|
||||
@@ -566,7 +566,7 @@
|
||||
"Maintenance Time Window of a Day": "Časový interval údržby cez deň",
|
||||
"Hello @everyone is...": "Dobrý deň, {'@'}všetci sú…",
|
||||
"clearHeartbeatsMsg": "Naozaj chcete odstrániť všetky pulzy pre toto sledovanie?",
|
||||
"Trust Proxy": "Dôveryhodná proxy",
|
||||
"Trust Proxy": "Dôverovať proxy",
|
||||
"RadiusCalledStationId": "ID volaného zariadenia",
|
||||
"Connection String": "Reťazec pripojenia",
|
||||
"socket": "Socket",
|
||||
@@ -574,7 +574,7 @@
|
||||
"confirmClearStatisticsMsg": "Naozaj chcete odstrániť VŠETKY štatistiky?",
|
||||
"-year": "-rok",
|
||||
"and": "a",
|
||||
"shrinkDatabaseDescriptionSqlite": "Podmienka spustenia príkazu pre SQLite databázu. Príkaz {auto_vacuum} je už zapnutý, ale nedochádza k defragmentácii databázy ani k prebaleniu jednotlivých stránok databázy ako to robí príkaz {vacuum}.",
|
||||
"shrinkDatabaseDescriptionSqlite": "Spusti prečistenie databázy {vacuum} pre SQLite. {auto_vacuum} je už povolené, ale to nedefragmentuje databázu ani neprebalí jednotlivé stránky databázy tak, ako to robí príkaz {vacuum}.",
|
||||
"lineDevConsoleTo": "Konzola Line Developers - {0}",
|
||||
"clearEventsMsg": "Naozaj chcete odstrániť všetky udalosti pre toto sledovanie?",
|
||||
"now": "teraz",
|
||||
@@ -638,10 +638,10 @@
|
||||
"defaultFriendlyName": "Nové sledovanie",
|
||||
"promosmsPassword": "Heslo API",
|
||||
"WebHookUrl": "URL webhooku",
|
||||
"Add Tags": "Pridať značky",
|
||||
"tagAlreadyStaged": "Táto značka (názov a hodnota) je už pripravená pre tento batch.",
|
||||
"Add Tags": "Pridať štítky",
|
||||
"tagAlreadyStaged": "Tento štítok (názov a hodnota) je už pripravený pre túto dávku.",
|
||||
"tagAlreadyOnMonitor": "Tento štítok (názov a hodnota) je už sledovaný alebo čaká na pridanie.",
|
||||
"tagNameExists": "Systémová značka s týmto názvom už existuje. Vyberte ju zo zoznamu alebo použite iný názov.",
|
||||
"tagNameExists": "Systémový štítok s týmto názvom už existuje. Vyberte ho zo zoznamu alebo použite iný názov.",
|
||||
"octopushAPIKey": "„Kľúč API“ z prihlasovacích údajov HTTP API v ovládacom paneli",
|
||||
"octopushLogin": "„Prihlásenie“ z prihlasovacích údajov HTTP API v ovládacom paneli",
|
||||
"pushoversounds pushover": "Pushover (predvolené)",
|
||||
@@ -1105,8 +1105,8 @@
|
||||
"pingNumericDescription": "Ak je táto možnosť označená, namiesto symbolických názvov hostiteľov sa budú zobrazovať IP adresy",
|
||||
"smtpHelpText": "„SMTPS“ testuje, či SMTP/TLS funguje; „“Ignorovať TLS“ sa pripája cez nezabezpečený text; „STARTTLS“ sa prippojí, vydá príkaz STARTTLS a overí certifikát servera. Žiadna z týchto možností neodosiela e-mail.",
|
||||
"rabbitmqNodesDescription": "Zadajte URL adresu pre uzly na správu RabbitMQ vrátane protokolu a portu. Príklad: {0}",
|
||||
"ipFamilyDescriptionAutoSelect": "Používa {happyEyeballs} na určenie IP rodiny.",
|
||||
"Ip Family": "IP rodina",
|
||||
"ipFamilyDescriptionAutoSelect": "Používa {happyEyeballs} na určenie IP verzie.",
|
||||
"Ip Family": "Verzia IP",
|
||||
"Manual": "Manuálne",
|
||||
"OAuth Audience": "OAuth publikum",
|
||||
"alertaRecoverState": "Obnoviť stav",
|
||||
@@ -1127,7 +1127,7 @@
|
||||
"pingGlobalTimeoutLabel": "Globálny časový limit",
|
||||
"pingGlobalTimeoutDescription": "Celkový čas v sekundách, po ktorom ping prestane bez ohľadu na odoslané pakety",
|
||||
"pingPerRequestTimeoutLabel": "Časový limit pre jednotlivý ping",
|
||||
"Staged Tags for Batch Add": "Fázované značky pre hromadné pridávanie",
|
||||
"Staged Tags for Batch Add": "Fázované štítky pre hromadné pridávanie",
|
||||
"senderSevenIO": "Odosielanie čísla alebo mena",
|
||||
"pingIntervalAdjustedInfo": "Interval upravený na základe počtu paketov, globálneho časového limitu a časového limitu pre jednotlivý ping",
|
||||
"FlashDuty Severity": "Závažnosť",
|
||||
@@ -1165,5 +1165,22 @@
|
||||
"Conversation token": "Token konverzácie",
|
||||
"Installing a Nextcloud Talk bot requires administrative access to the server.": "Inštalácia bota Nextcloud Talk vyžaduje prístup správcu k serveru.",
|
||||
"Send UP silently": "Odoslať DOSTUPNÉ potichu",
|
||||
"auto-select": "Automaticky vybrať"
|
||||
"auto-select": "Automaticky vybrať",
|
||||
"Enter a list of userId": "Zadajte zoznam používateľských identifikátorov",
|
||||
"wayToGetBaleChatID": "Svoje chat ID získate tak, že pošlete správu botovi a prejdete na túto URL adresu, kde sa zobrazí chat_id:",
|
||||
"wayToGetBaleToken": "Token môžete získať z {0}.",
|
||||
"supportBaleChatID": "Podpora pre Priamy chat / Skupinu / ID chatu kanála",
|
||||
"Invalid userId": "Neplatné userId [{userId}]",
|
||||
"Mention User List": "Zoznam používateľských ID Mention",
|
||||
"Dingtalk Mobile List": "Zoznam mobilov",
|
||||
"Dingtalk User List": "Zoznam používateľských ID",
|
||||
"Enter a list of mobile": "Zadajte zoznam mobilov",
|
||||
"Invalid mobile": "Neplatný mobil [{mobile}]",
|
||||
"Mention Mobile List": "Zoznam mobilov Mention",
|
||||
"Maximum Retries": "Maximálny počet opakovaní",
|
||||
"Number of retry attempts if webhook fails": "Počet pokusov o opakovanie (každých 60–180 sekúnd), ak webhook zlyhá.",
|
||||
"webhookGetMethodDesc": "GET odosiela údaje ako parametre dopytu a neumožňuje konfiguráciu tela. Je užitočné na spúšťanie Push monitorov Uptime Kuma.",
|
||||
"descriptionHelpText": "Zobrazené na internom paneli. Markdown je povolený a pred zobrazením je očistený (zachováva medzery a odsadenie).",
|
||||
"HTTP Method": "Metóda HTTP",
|
||||
"webhookPostMethodDesc": "POST je vhodný pre väčšinu moderných HTTP serverov."
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"languageName": "İngilizce",
|
||||
"languageName": "Türkçe",
|
||||
"checkEverySecond": "{0} saniyede bir kontrol et",
|
||||
"retryCheckEverySecond": "{0} saniyede bir dene",
|
||||
"resendEveryXTimes": "Her {0} bir yeniden gönder",
|
||||
@@ -526,18 +526,18 @@
|
||||
"Most likely causes:": "En olası nedenler:",
|
||||
"The resource is no longer available.": "Kaynak artık mevcut değil.",
|
||||
"There might be a typing error in the address.": "Adreste bir yazım hatası olabilir.",
|
||||
"What you can try:": "Ne deneyebilirsin:",
|
||||
"What you can try:": "Deneyebileceğin şeyler:",
|
||||
"Retype the address.": "Adresi tekrar yazın.",
|
||||
"Go back to the previous page.": "Bir önceki sayfaya geri git.",
|
||||
"Coming Soon": "Yakında gelecek",
|
||||
"Coming Soon": "Yakında Gelecek",
|
||||
"wayToGetClickSendSMSToken": "API Kullanıcı Adı ve API Anahtarını {0} adresinden alabilirsiniz.",
|
||||
"Connection String": "Bağlantı dizisi",
|
||||
"Connection String": "Bağlantı Dizisi",
|
||||
"Query": "Sorgu",
|
||||
"settingsCertificateExpiry": "TLS Sertifikasının Geçerlilik Süresi",
|
||||
"certificationExpiryDescription": "HTTPS Monitörleri, TLS sertifikasının süresi dolduğunda bildirimi tetikler:",
|
||||
"Setup Docker Host": "Docker Ana Bilgisayarını Ayarla",
|
||||
"Connection Type": "Bağlantı türü",
|
||||
"Docker Daemon": "Docker Daemon",
|
||||
"Docker Daemon": "Docker Servisi",
|
||||
"deleteDockerHostMsg": "Bu docker ana bilgisayarını tüm monitörler için silmek istediğinizden emin misiniz?",
|
||||
"socket": "Soket",
|
||||
"tcp": "TCP / HTTP",
|
||||
@@ -840,11 +840,11 @@
|
||||
"styleElapsedTime": "Kalp atışı çubuğunun altında geçen süre",
|
||||
"styleElapsedTimeShowWithLine": "Göster (Satır ile birlikte)",
|
||||
"enableNSCD": "Tüm DNS isteklerini önbelleğe almak için NSCD'yi (Ad Hizmeti Önbellek Programı) etkinleştirin",
|
||||
"setupDatabaseEmbeddedMariaDB": "Hiçbir şey ayarlamanıza gerek yok. Bu docker imajı, MariaDB'yi sizin için otomatik olarak yerleştirdi ve yapılandırdı. Çalışma Süresi Kuma bu veri tabanına unix soketi aracılığıyla bağlanacaktır.",
|
||||
"setupDatabaseSQLite": "Küçük ölçekli dağıtımlar için önerilen basit bir veritabanı dosyası. v2.0.0'dan önce Uptime Kuma, varsayılan veritabanı olarak SQLite'ı kullanıyordu.",
|
||||
"setupDatabaseChooseDatabase": "Hangi veri tabanını kullanmak istersiniz?",
|
||||
"setupDatabaseEmbeddedMariaDB": "Hiçbir şey ayarlamanıza gerek yok. Bu Docker görseli, MariaDB’yi otomatik olarak gömülü ve yapılandırılmış şekilde içerir. Uptime Kuma bu veri tabanına unix soketi aracılığıyla bağlanacaktır.",
|
||||
"setupDatabaseSQLite": "Küçük ölçekli dağıtımlar için önerilen basit bir veri tabanı dosyası. v2.0.0'dan önce Uptime Kuma, varsayılan veri tabanı olarak SQLite'ı kullanıyordu.",
|
||||
"setupDatabaseChooseDatabase": "Hangi veritabanını kullanmak istersiniz?",
|
||||
"setupDatabaseMariaDB": "Harici bir MariaDB veri tabanına bağlanın. Veri tabanı bağlantı bilgilerini ayarlamanız gerekir.",
|
||||
"dbName": "Veri tabanı ismi",
|
||||
"dbName": "Veri Tabanı Adı",
|
||||
"Saved.": "Kaydedildi.",
|
||||
"toastErrorTimeout": "Hata Bildirimleri için Zaman Aşımı",
|
||||
"toastSuccessTimeout": "Başarı Bildirimleri için Zaman Aşımı",
|
||||
@@ -913,7 +913,7 @@
|
||||
"Add a domain": "Alan adı ekle",
|
||||
"Search monitored sites": "İzlenen siteleri arayın",
|
||||
"ntfyPriorityHelptextAllEvents": "Tüm olaylar maksimum öncelik ile gönderilir",
|
||||
"settingUpDatabaseMSG": "Veritabanı kuruluyor. Biraz zaman alabilir, lütfen sabırlı olun.",
|
||||
"settingUpDatabaseMSG": "Veri tabanı kuruluyor. Biraz zaman alabilir, lütfen sabırlı olun.",
|
||||
"statusPageSpecialSlugDesc": "Özel slug {0}: slug belirtilmediğinde bu sayfa gösterilecektir",
|
||||
"ntfyPriorityHelptextAllExceptDown": "Önceliği {1} olan {0}-olayları hariç tüm olaylar bu öncelik ile gönderilir",
|
||||
"What is a Remote Browser?": "Uzak Tarayıcı Nedir?",
|
||||
@@ -1175,5 +1175,21 @@
|
||||
"tagNameExists": "Bu isimde bir sistem etiketi zaten var. Listeden seçin veya farklı bir isim kullanın.",
|
||||
"Clear Form": "Formu Temizle",
|
||||
"Optional: The audience to request the JWT for": "İsteğe bağlı: JWT'nin talep edileceği kitle",
|
||||
"OAuth Audience": "OAuth Kitlesi"
|
||||
"OAuth Audience": "OAuth Kitlesi",
|
||||
"mqttWebSocketPath": "MQTT WebSocket Yolu",
|
||||
"Path": "Yol",
|
||||
"mqttWebsocketPathExplanation": "WebSocket bağlantıları üzerinden MQTT için WebSocket yolu (örn. /mqtt)",
|
||||
"clearAllEventsMsg": "Tüm etkinlikleri silmek istediğinizden emin misiniz?",
|
||||
"Template plain text instead of using cards": "Kartlar yerine düz metin şablonu",
|
||||
"auto-select": "Otomatik Seç",
|
||||
"Could not clear events": "{failed}/{total} etkinlik temizlenemedi",
|
||||
"Clear All Events": "Tüm Etkinlikleri Temizle",
|
||||
"mqttWebsocketPathInvalid": "Lütfen geçerli bir WebSocket yolu formatı kullanın",
|
||||
"mqttHostnameTip": "Lütfen bu formatı kullanın {hostnameFormat}",
|
||||
"wayToGetBaleChatID": "Bot’a bir mesaj göndererek ve şu URL’ye giderek chat_id’nizi görüntüleyebilirsiniz:",
|
||||
"wayToGetBaleToken": "{0} üzerinden bir token alabilirsiniz.",
|
||||
"supportBaleChatID": "Destek Direkt Mesaj / Grup / Kanalın Mesaj ID'si",
|
||||
"Events cleared successfully": "Etkinlikler başarıyla temizlendi.",
|
||||
"No monitors found": "Monitör bulunamadı.",
|
||||
"issueWithGoogleChatOnAndroidHelptext": "Bu ayrıca {issuetackerURL} gibi üst akıştaki hataları aşmayı sağlar"
|
||||
}
|
||||
|
||||
@@ -819,6 +819,7 @@
|
||||
<div class="my-3">
|
||||
<label for="description" class="form-label">{{ $t("Description") }}</label>
|
||||
<input id="description" v-model="monitor.description" type="text" class="form-control">
|
||||
<div class="form-text">{{ $t("descriptionHelpText") }}</div>
|
||||
</div>
|
||||
|
||||
<div class="my-3">
|
||||
|
||||
@@ -219,7 +219,7 @@ class RelativeTimeFormatter {
|
||||
* Default locale and options for Relative Time Formatter
|
||||
*/
|
||||
constructor() {
|
||||
this.options = { numeric: "auto" };
|
||||
this.options = { numeric: "always" };
|
||||
this.instance = new Intl.RelativeTimeFormat(currentLocale(), this.options);
|
||||
}
|
||||
|
||||
@@ -267,7 +267,7 @@ class RelativeTimeFormatter {
|
||||
};
|
||||
|
||||
if (days > 0) {
|
||||
toFormattedPart(days, "days");
|
||||
toFormattedPart(days, "day");
|
||||
}
|
||||
if (hours > 0) {
|
||||
toFormattedPart(hours, "hour");
|
||||
|
||||
468
src/util.js
468
src/util.js
@@ -1,468 +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_FgPink = exports.CONSOLE_STYLE_FgBrown = exports.CONSOLE_STYLE_FgViolet = 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.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.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.evaluateJsonQuery = exports.intHash = exports.localToUTC = exports.utcToLocal = exports.utcToISODateTime = exports.isoToUTCDateTime = exports.parseTimeFromTimeObject = exports.parseTimeObject = exports.getMaintenanceRelativeURL = 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 = 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 = 20;
|
||||
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.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("", msg, "debug");
|
||||
}
|
||||
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, msg, level) {
|
||||
if (level === "DEBUG" && !exports.isDev) {
|
||||
return;
|
||||
}
|
||||
if (this.hideLog[level] && this.hideLog[level].includes(module.toLowerCase())) {
|
||||
return;
|
||||
}
|
||||
module = module.toUpperCase();
|
||||
level = level.toUpperCase();
|
||||
let now;
|
||||
if (dayjs.tz) {
|
||||
now = dayjs.tz(new Date()).format();
|
||||
}
|
||||
else {
|
||||
now = dayjs().format();
|
||||
}
|
||||
const levelColor = consoleLevelColors[level];
|
||||
const moduleColor = consoleModuleColors[intHash(module, consoleModuleColors.length)];
|
||||
let timePart;
|
||||
let modulePart;
|
||||
let levelPart;
|
||||
let msgPart;
|
||||
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;
|
||||
switch (level) {
|
||||
case "ERROR":
|
||||
if (typeof msg === "string") {
|
||||
msgPart = exports.CONSOLE_STYLE_FgRed + msg + exports.CONSOLE_STYLE_Reset;
|
||||
}
|
||||
else {
|
||||
msgPart = msg;
|
||||
}
|
||||
break;
|
||||
case "DEBUG":
|
||||
if (typeof msg === "string") {
|
||||
msgPart = exports.CONSOLE_STYLE_FgGray + msg + exports.CONSOLE_STYLE_Reset;
|
||||
}
|
||||
else {
|
||||
msgPart = msg;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
msgPart = msg;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
timePart = now;
|
||||
modulePart = `[${module}]`;
|
||||
levelPart = `${level}:`;
|
||||
msgPart = msg;
|
||||
}
|
||||
switch (level) {
|
||||
case "ERROR":
|
||||
console.error(timePart, modulePart, levelPart, msgPart);
|
||||
break;
|
||||
case "WARN":
|
||||
console.warn(timePart, modulePart, levelPart, msgPart);
|
||||
break;
|
||||
case "INFO":
|
||||
console.info(timePart, modulePart, levelPart, msgPart);
|
||||
break;
|
||||
case "DEBUG":
|
||||
if (exports.isDev) {
|
||||
console.debug(timePart, modulePart, levelPart, msgPart);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
console.log(timePart, modulePart, levelPart, msgPart);
|
||||
break;
|
||||
}
|
||||
}
|
||||
info(module, msg) {
|
||||
this.log(module, msg, "info");
|
||||
}
|
||||
warn(module, msg) {
|
||||
this.log(module, msg, "warn");
|
||||
}
|
||||
error(module, msg) {
|
||||
this.log(module, msg, "error");
|
||||
}
|
||||
debug(module, msg) {
|
||||
this.log(module, msg, "debug");
|
||||
}
|
||||
exception(module, exception, msg) {
|
||||
let finalMessage = exception;
|
||||
if (msg) {
|
||||
finalMessage = `${msg}: ${exception}`;
|
||||
}
|
||||
this.log(module, finalMessage, "error");
|
||||
}
|
||||
}
|
||||
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 getMaintenanceRelativeURL(id) {
|
||||
return "/maintenance/" + id;
|
||||
}
|
||||
exports.getMaintenanceRelativeURL = getMaintenanceRelativeURL;
|
||||
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 (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;
|
||||
64
src/util.ts
64
src/util.ts
@@ -193,7 +193,7 @@ export function ucfirst(str: string) {
|
||||
* @returns {void}
|
||||
*/
|
||||
export function debug(msg: unknown) {
|
||||
log.log("", msg, "debug");
|
||||
log.log("", "debug", msg);
|
||||
}
|
||||
|
||||
class Logger {
|
||||
@@ -238,11 +238,11 @@ class Logger {
|
||||
/**
|
||||
* Write a message to the log
|
||||
* @param module The module the log comes from
|
||||
* @param msg Message to write
|
||||
* @param level Log level. One of INFO, WARN, ERROR, DEBUG or can be customized.
|
||||
* @param msg Message to write
|
||||
* @returns {void}
|
||||
*/
|
||||
log(module: string, msg: any, level: string) {
|
||||
log(module: string, level: string, ...msg: unknown[]) {
|
||||
if (level === "DEBUG" && !isDev) {
|
||||
return;
|
||||
}
|
||||
@@ -267,7 +267,6 @@ class Logger {
|
||||
let timePart: string;
|
||||
let modulePart: string;
|
||||
let levelPart: string;
|
||||
let msgPart: string;
|
||||
|
||||
if (isNode) {
|
||||
// Add console colors
|
||||
@@ -281,54 +280,33 @@ class Logger {
|
||||
}
|
||||
|
||||
modulePart = "[" + moduleColor + module + CONSOLE_STYLE_Reset + "]";
|
||||
|
||||
levelPart = levelColor + `${level}:` + CONSOLE_STYLE_Reset;
|
||||
|
||||
switch (level) {
|
||||
case "ERROR":
|
||||
if (typeof msg === "string") {
|
||||
msgPart = CONSOLE_STYLE_FgRed + msg + CONSOLE_STYLE_Reset;
|
||||
} else {
|
||||
msgPart = msg;
|
||||
}
|
||||
break;
|
||||
case "DEBUG":
|
||||
if (typeof msg === "string") {
|
||||
msgPart = CONSOLE_STYLE_FgGray + msg + CONSOLE_STYLE_Reset;
|
||||
} else {
|
||||
msgPart = msg;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
msgPart = msg;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
// No console colors
|
||||
timePart = now;
|
||||
modulePart = `[${module}]`;
|
||||
levelPart = `${level}:`;
|
||||
msgPart = msg;
|
||||
}
|
||||
|
||||
// Write to console
|
||||
switch (level) {
|
||||
case "ERROR":
|
||||
console.error(timePart, modulePart, levelPart, msgPart);
|
||||
console.error(timePart, modulePart, levelPart, ...msg);
|
||||
break;
|
||||
case "WARN":
|
||||
console.warn(timePart, modulePart, levelPart, msgPart);
|
||||
console.warn(timePart, modulePart, levelPart, ...msg);
|
||||
break;
|
||||
case "INFO":
|
||||
console.info(timePart, modulePart, levelPart, msgPart);
|
||||
console.info(timePart, modulePart, levelPart, ...msg);
|
||||
break;
|
||||
case "DEBUG":
|
||||
if (isDev) {
|
||||
console.debug(timePart, modulePart, levelPart, msgPart);
|
||||
console.debug(timePart, modulePart, levelPart, ...msg);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
console.log(timePart, modulePart, levelPart, msgPart);
|
||||
console.log(timePart, modulePart, levelPart, ...msg);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -339,8 +317,8 @@ class Logger {
|
||||
* @param msg Message to write
|
||||
* @returns {void}
|
||||
*/
|
||||
info(module: string, msg: unknown) {
|
||||
this.log(module, msg, "info");
|
||||
info(module: string, ...msg: unknown[]) {
|
||||
this.log(module, "info", ...msg);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -349,8 +327,8 @@ class Logger {
|
||||
* @param msg Message to write
|
||||
* @returns {void}
|
||||
*/
|
||||
warn(module: string, msg: unknown) {
|
||||
this.log(module, msg, "warn");
|
||||
warn(module: string, ...msg: unknown[]) {
|
||||
this.log(module, "warn", ...msg);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -359,8 +337,8 @@ class Logger {
|
||||
* @param msg Message to write
|
||||
* @returns {void}
|
||||
*/
|
||||
error(module: string, msg: unknown) {
|
||||
this.log(module, msg, "error");
|
||||
error(module: string, ...msg: unknown[]) {
|
||||
this.log(module, "error", ...msg);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -369,8 +347,8 @@ class Logger {
|
||||
* @param msg Message to write
|
||||
* @returns {void}
|
||||
*/
|
||||
debug(module: string, msg: unknown) {
|
||||
this.log(module, msg, "debug");
|
||||
debug(module: string, ...msg: unknown[]) {
|
||||
this.log(module, "debug", ...msg);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -380,14 +358,8 @@ class Logger {
|
||||
* @param msg The message to write
|
||||
* @returns {void}
|
||||
*/
|
||||
exception(module: string, exception: unknown, msg: unknown) {
|
||||
let finalMessage = exception;
|
||||
|
||||
if (msg) {
|
||||
finalMessage = `${msg}: ${exception}`;
|
||||
}
|
||||
|
||||
this.log(module, finalMessage, "error");
|
||||
exception(module: string, exception: unknown, ...msg: unknown[]) {
|
||||
this.log(module, "error", ...msg, exception);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,16 +10,18 @@ const { UP, PENDING } = require("../../src/util");
|
||||
* @param {string} mqttSuccessMessage the message that the monitor expects
|
||||
* @param {null|"keyword"|"json-query"} mqttCheckType the type of check we perform
|
||||
* @param {string} receivedMessage what message is received from the mqtt channel
|
||||
* @param {string} monitorTopic which MQTT topic is monitored (wildcards are allowed)
|
||||
* @param {string} publishTopic to which MQTT topic the message is sent
|
||||
* @returns {Promise<Heartbeat>} the heartbeat produced by the check
|
||||
*/
|
||||
async function testMqtt(mqttSuccessMessage, mqttCheckType, receivedMessage) {
|
||||
async function testMqtt(mqttSuccessMessage, mqttCheckType, receivedMessage, monitorTopic = "test", publishTopic = "test") {
|
||||
const hiveMQContainer = await new HiveMQContainer().start();
|
||||
const connectionString = hiveMQContainer.getConnectionString();
|
||||
const mqttMonitorType = new MqttMonitorType();
|
||||
const monitor = {
|
||||
jsonPath: "firstProp", // always return firstProp for the json-query monitor
|
||||
hostname: connectionString.split(":", 2).join(":"),
|
||||
mqttTopic: "test",
|
||||
mqttTopic: monitorTopic,
|
||||
port: connectionString.split(":")[2],
|
||||
mqttUsername: null,
|
||||
mqttPassword: null,
|
||||
@@ -36,9 +38,9 @@ async function testMqtt(mqttSuccessMessage, mqttCheckType, receivedMessage) {
|
||||
|
||||
const testMqttClient = mqtt.connect(hiveMQContainer.getConnectionString());
|
||||
testMqttClient.on("connect", () => {
|
||||
testMqttClient.subscribe("test", (error) => {
|
||||
testMqttClient.subscribe(monitorTopic, (error) => {
|
||||
if (!error) {
|
||||
testMqttClient.publish("test", receivedMessage);
|
||||
testMqttClient.publish(publishTopic, receivedMessage);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -53,7 +55,7 @@ async function testMqtt(mqttSuccessMessage, mqttCheckType, receivedMessage) {
|
||||
}
|
||||
|
||||
describe("MqttMonitorType", {
|
||||
concurrency: true,
|
||||
concurrency: 4,
|
||||
skip: !!process.env.CI && (process.platform !== "linux" || process.arch !== "x64")
|
||||
}, () => {
|
||||
test("valid keywords (type=default)", async () => {
|
||||
@@ -62,11 +64,63 @@ describe("MqttMonitorType", {
|
||||
assert.strictEqual(heartbeat.msg, "Topic: test; Message: -> KEYWORD <-");
|
||||
});
|
||||
|
||||
test("valid nested topic", async () => {
|
||||
const heartbeat = await testMqtt("KEYWORD", null, "-> KEYWORD <-", "a/b/c", "a/b/c");
|
||||
assert.strictEqual(heartbeat.status, UP);
|
||||
assert.strictEqual(heartbeat.msg, "Topic: a/b/c; Message: -> KEYWORD <-");
|
||||
});
|
||||
|
||||
test("valid nested topic (with special chars)", async () => {
|
||||
const heartbeat = await testMqtt("KEYWORD", null, "-> KEYWORD <-", "a/'/$/./*/%", "a/'/$/./*/%");
|
||||
assert.strictEqual(heartbeat.status, UP);
|
||||
assert.strictEqual(heartbeat.msg, "Topic: a/'/$/./*/%; Message: -> KEYWORD <-");
|
||||
});
|
||||
|
||||
test("valid wildcard topic (with #)", async () => {
|
||||
const heartbeat = await testMqtt("KEYWORD", null, "-> KEYWORD <-", "a/#", "a/b/c");
|
||||
assert.strictEqual(heartbeat.status, UP);
|
||||
assert.strictEqual(heartbeat.msg, "Topic: a/b/c; Message: -> KEYWORD <-");
|
||||
});
|
||||
|
||||
test("valid wildcard topic (with +)", async () => {
|
||||
const heartbeat = await testMqtt("KEYWORD", null, "-> KEYWORD <-", "a/+/c", "a/b/c");
|
||||
assert.strictEqual(heartbeat.status, UP);
|
||||
assert.strictEqual(heartbeat.msg, "Topic: a/b/c; Message: -> KEYWORD <-");
|
||||
});
|
||||
|
||||
test("valid wildcard topic (with + and #)", async () => {
|
||||
const heartbeat = await testMqtt("KEYWORD", null, "-> KEYWORD <-", "a/+/c/#", "a/b/c/d/e");
|
||||
assert.strictEqual(heartbeat.status, UP);
|
||||
assert.strictEqual(heartbeat.msg, "Topic: a/b/c/d/e; Message: -> KEYWORD <-");
|
||||
});
|
||||
|
||||
test("invalid topic", async () => {
|
||||
await assert.rejects(
|
||||
testMqtt("keyword will not be checked anyway", null, "message", "x/y/z", "a/b/c"),
|
||||
new Error("Timeout, Message not received"),
|
||||
);
|
||||
});
|
||||
|
||||
test("invalid wildcard topic (with #)", async () => {
|
||||
await assert.rejects(
|
||||
testMqtt("", null, "# should be last character", "#/c", "a/b/c"),
|
||||
new Error("Timeout, Message not received"),
|
||||
);
|
||||
});
|
||||
|
||||
test("invalid wildcard topic (with +)", async () => {
|
||||
await assert.rejects(
|
||||
testMqtt("", null, "message", "x/+/z", "a/b/c"),
|
||||
new Error("Timeout, Message not received"),
|
||||
);
|
||||
});
|
||||
|
||||
test("valid keywords (type=keyword)", async () => {
|
||||
const heartbeat = await testMqtt("KEYWORD", "keyword", "-> KEYWORD <-");
|
||||
assert.strictEqual(heartbeat.status, UP);
|
||||
assert.strictEqual(heartbeat.msg, "Topic: test; Message: -> KEYWORD <-");
|
||||
});
|
||||
|
||||
test("invalid keywords (type=default)", async () => {
|
||||
await assert.rejects(
|
||||
testMqtt("NOT_PRESENT", null, "-> KEYWORD <-"),
|
||||
@@ -80,12 +134,14 @@ describe("MqttMonitorType", {
|
||||
new Error("Message Mismatch - Topic: test; Message: -> KEYWORD <-"),
|
||||
);
|
||||
});
|
||||
|
||||
test("valid json-query", async () => {
|
||||
// works because the monitors' jsonPath is hard-coded to "firstProp"
|
||||
const heartbeat = await testMqtt("present", "json-query", "{\"firstProp\":\"present\"}");
|
||||
assert.strictEqual(heartbeat.status, UP);
|
||||
assert.strictEqual(heartbeat.msg, "Message received, expected value is found");
|
||||
});
|
||||
|
||||
test("invalid (because query fails) json-query", async () => {
|
||||
// works because the monitors' jsonPath is hard-coded to "firstProp"
|
||||
await assert.rejects(
|
||||
@@ -93,6 +149,7 @@ describe("MqttMonitorType", {
|
||||
new Error("Message received but value is not equal to expected value, value was: [undefined]"),
|
||||
);
|
||||
});
|
||||
|
||||
test("invalid (because successMessage fails) json-query", async () => {
|
||||
// works because the monitors' jsonPath is hard-coded to "firstProp"
|
||||
await assert.rejects(
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"esModuleInterop": false
|
||||
},
|
||||
"files": [
|
||||
"./src/util.ts"
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user