Compare commits

...

45 Commits

Author SHA1 Message Date
Louis Lam
fa2bc8eda6 Merge branch '3.0.X' into dev-better-auth 2025-11-05 22:05:07 +08:00
Louis Lam
93fc8e463f [3.0.0] Project Upgrade (#6310) 2025-11-05 21:54:55 +08:00
Osman Karagöz
6dfa574e36 Fix: monitor(tailscale): Check exit code before failing on stderr output (#6309) 2025-11-04 18:20:39 +01:00
Louis Lam
08d77e6fce Fix build issue on Node.js 25 (#6295) 2025-11-04 06:55:00 +08:00
Dorian Grasset
5207ba6d97 fix: child monitors disappear after group deletion (#6287)
Co-authored-by: Louis Lam <louislam@users.noreply.github.com>
2025-11-03 21:21:36 +01:00
Louis Lam
a52186cf7e refactor(logging): improve log function parameters (#6298) 2025-11-02 02:52:40 +08:00
Louis Lam
9fb4263427 Add Copilot instructions (#6290)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-10-30 13:23:27 +08:00
Louis Lam
79c844d598 Add no-audit to the setup script (#6288) 2025-10-30 11:36:00 +08:00
Louis Lam
7f294c2c25 README Improvements (#6283) 2025-10-29 15:57:57 +08:00
Louis Lam
e505cb56b4 Translations Update from Weblate (#6207) 2025-10-29 03:06:43 +08:00
Gringo
d170e54a00 Translated using Weblate (Italian)
Currently translated at 100.0% (1174 of 1174 strings)

Co-authored-by: Gringo <ita.translations@tiscali.it>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/it/
Translation: Uptime Kuma/Uptime Kuma
2025-10-28 18:20:19 +00:00
Taskin Khaleque
acda7f720f Translated using Weblate (Bengali)
Currently translated at 22.6% (266 of 1174 strings)

Co-authored-by: Taskin Khaleque <taskin0850@gmail.com>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/bn/
Translation: Uptime Kuma/Uptime Kuma
2025-10-28 18:20:19 +00:00
Aarón Rosa Díaz
3466f8e9f7 Translated using Weblate (Spanish)
Currently translated at 100.0% (1174 of 1174 strings)

Co-authored-by: Aarón Rosa Díaz <sraaronrock@tuta.io>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/es/
Translation: Uptime Kuma/Uptime Kuma
2025-10-28 18:20:19 +00:00
Buchtič
18cb1ec9c0 Translated using Weblate (Czech)
Currently translated at 100.0% (1174 of 1174 strings)

Co-authored-by: Buchtič <martin.buchta@gmail.com>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/cs/
Translation: Uptime Kuma/Uptime Kuma
2025-10-28 18:20:19 +00:00
Davide Di Caro
ed18d06cbb Translated using Weblate (Italian)
Currently translated at 100.0% (1174 of 1174 strings)

Co-authored-by: Davide Di Caro <davidedcr1@gmail.com>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/it/
Translation: Uptime Kuma/Uptime Kuma
2025-10-28 18:20:19 +00:00
gp2pepe
34dcbf9d69 Translated using Weblate (Spanish)
Currently translated at 100.0% (1172 of 1172 strings)

Co-authored-by: gp2pepe <gperezpepe@gmail.com>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/es/
Translation: Uptime Kuma/Uptime Kuma
2025-10-28 18:20:19 +00:00
Davit Chinchaladze
da257248ef Translated using Weblate (Georgian)
Currently translated at 6.5% (77 of 1172 strings)

Co-authored-by: Davit Chinchaladze <datotoda1342+kuma@gmail.com>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/ka/
Translation: Uptime Kuma/Uptime Kuma
2025-10-28 18:20:19 +00:00
Adam Stachowicz
a8b4d35b2b Translated using Weblate (Polish)
Currently translated at 100.0% (1172 of 1172 strings)

Translated using Weblate (Polish)

Currently translated at 99.8% (1170 of 1172 strings)

Co-authored-by: Adam Stachowicz <saibamenppl@gmail.com>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/pl/
Translation: Uptime Kuma/Uptime Kuma
2025-10-28 18:20:19 +00:00
LEE MIN GYU
592b4cd712 Translated using Weblate (Korean)
Currently translated at 79.0% (927 of 1172 strings)

Co-authored-by: LEE MIN GYU <lee101570@gmail.com>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/ko/
Translation: Uptime Kuma/Uptime Kuma
2025-10-28 18:20:18 +00:00
Kang Dongheon
b40bbdf68a Translated using Weblate (Korean)
Currently translated at 76.4% (896 of 1172 strings)

Co-authored-by: Kang Dongheon <daniel2231.dev@gmail.com>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/ko/
Translation: Uptime Kuma/Uptime Kuma
2025-10-28 18:20:18 +00:00
LEE MIN GYU
14aaa10251 Translated using Weblate (Korean)
Currently translated at 76.4% (896 of 1172 strings)

Co-authored-by: LEE MIN GYU <lee101570@gmail.com>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/ko/
Translation: Uptime Kuma/Uptime Kuma
2025-10-28 18:20:18 +00:00
Gringo
18813f90c8 Translated using Weblate (Italian)
Currently translated at 100.0% (1172 of 1172 strings)

Co-authored-by: Gringo <ita.translations@tiscali.it>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/it/
Translation: Uptime Kuma/Uptime Kuma
2025-10-28 18:20:18 +00:00
Aluisio
76c4b4649a Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (1172 of 1172 strings)

Co-authored-by: Aluisio <aluisiodeavila@hotmail.com>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/pt_BR/
Translation: Uptime Kuma/Uptime Kuma
2025-10-28 18:20:18 +00:00
Aindriú Mac Giolla Eoin
3b3eadf298 Translated using Weblate (Irish)
Currently translated at 100.0% (1172 of 1172 strings)

Co-authored-by: Aindriú Mac Giolla Eoin <aindriu80@gmail.com>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/ga/
Translation: Uptime Kuma/Uptime Kuma
2025-10-28 18:20:18 +00:00
Marco
c608917611 Translated using Weblate (German)
Currently translated at 100.0% (1178 of 1178 strings)

Translated using Weblate (German (Switzerland))

Currently translated at 100.0% (1178 of 1178 strings)

Translated using Weblate (German)

Currently translated at 100.0% (1174 of 1174 strings)

Translated using Weblate (German (Switzerland))

Currently translated at 100.0% (1174 of 1174 strings)

Translated using Weblate (German)

Currently translated at 100.0% (1172 of 1172 strings)

Translated using Weblate (German (Switzerland))

Currently translated at 100.0% (1172 of 1172 strings)

Co-authored-by: Marco <marco@nanoweb.ch>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/de/
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/de_CH/
Translation: Uptime Kuma/Uptime Kuma
2025-10-28 18:20:18 +00:00
Alican Akyıldız
4aec8c8b44 Translated using Weblate (Turkish)
Currently translated at 97.5% (1143 of 1172 strings)

Translated using Weblate (Turkish)

Currently translated at 96.3% (1129 of 1172 strings)

Co-authored-by: Alican Akyıldız <alican15033@gmail.com>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/tr/
Translation: Uptime Kuma/Uptime Kuma
2025-10-28 18:20:18 +00:00
MrEddX
b688fe07a7 Translated using Weblate (Bulgarian)
Currently translated at 100.0% (1174 of 1174 strings)

Translated using Weblate (Bulgarian)

Currently translated at 100.0% (1172 of 1172 strings)

Co-authored-by: MrEddX <mreddx@chatrix.one>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/bg/
Translation: Uptime Kuma/Uptime Kuma
2025-10-28 18:20:18 +00:00
Jozef Gaal
e9d061f9a8 Translated using Weblate (Slovak)
Currently translated at 100.0% (1178 of 1178 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (1174 of 1174 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (1172 of 1172 strings)

Co-authored-by: Jozef Gaal <preklady@mayday.sk>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/sk/
Translation: Uptime Kuma/Uptime Kuma
2025-10-28 18:20:17 +00:00
Virenbar
48b25fa395 Translated using Weblate (Russian)
Currently translated at 99.0% (1161 of 1172 strings)

Co-authored-by: Virenbar <rib.artem@gmail.com>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/ru/
Translation: Uptime Kuma/Uptime Kuma
2025-10-28 18:20:17 +00:00
Cyril59310
6e26b3ef54 Translated using Weblate (French)
Currently translated at 100.0% (1178 of 1178 strings)

Translated using Weblate (French)

Currently translated at 100.0% (1174 of 1174 strings)

Translated using Weblate (French)

Currently translated at 100.0% (1172 of 1172 strings)

Co-authored-by: Cyril59310 <archas.cyril@hotmail.fr>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/fr/
Translation: Uptime Kuma/Uptime Kuma
2025-10-28 18:20:17 +00:00
Ivan Bratović
fcb2b923e0 Translated using Weblate (Croatian)
Currently translated at 100.0% (1178 of 1178 strings)

Translated using Weblate (Croatian)

Currently translated at 100.0% (1161 of 1161 strings)

Co-authored-by: Ivan Bratović <ivanbratovic4@gmail.com>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/hr/
Translation: Uptime Kuma/Uptime Kuma
2025-10-28 18:20:17 +00:00
aruj0
19c2bbd586 Feature/webhook get method support (#6194)
Co-authored-by: Frank Elsinga <frank@elsinga.de>
2025-10-27 22:19:05 +01:00
maldotcom2
38ec3bc432 Fix do nothing erroneous api call for Pagerduty (#6231)
Co-authored-by: Frank Elsinga <frank@elsinga.de>
2025-10-27 22:09:21 +01:00
Ashutosh Mohan
ea3a4f6963 feat(status-page): add help text for 'Description' in monitor edit status page (#6254)
Co-authored-by: Ashutosh Mohan <ashutosh.mohan@koottu.app>
Co-authored-by: Frank Elsinga <frank@elsinga.de>
2025-10-27 21:58:13 +01:00
Louis Lam
a3672a6afb Fix: disable eqeqeq for UPTIME_KUMA_LOG_RESPONSE_BODY_MONITOR_ID (#6271) 2025-10-28 04:01:24 +08:00
Louis Lam
afbd1ce0e9 [Eliminate Blocking] Real Browser Monitor + Check Apprise (#5924) 2025-10-28 00:27:29 +08:00
Louis Lam
8f3cb770eb [Docker] Bump to Node.js 22 (#6222)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-10-27 23:58:27 +08:00
Louis Lam
f6a47f351c Revert "feat(release): reduce image size by running autoremove, clean and removing lists" (#6268) 2025-10-27 22:10:29 +08:00
Justin Keller
93945606ea feat(release): reduce image size by running autoremove, clean and removing lists (#6267) 2025-10-27 13:12:53 +01:00
Eric Duminil
c3a62f7461 Allow MQTT topic to have wildcards (# or +) (#5398) 2025-10-26 20:36:47 +01:00
Tobi
7bf25ba1bf fix(auth/UX): trim username in login & setup (#6263) 2025-10-26 19:24:42 +01:00
Paulus Lucas
b7bb961eac Fix: release script do not update lock file correctly (#6257) 2025-10-26 17:10:16 +08:00
Louis Lam
83c3cfc8c0 2.0.X to master (#6226) 2025-10-25 05:22:13 +08:00
Max Michels
cd49700d3f Adding retries to Google Chat Notifications #6242 (#6245)
Co-authored-by: Frank Elsinga <frank@elsinga.de>
2025-10-24 21:50:25 +02:00
Abass 🍉
9a3613856c Change Relative Time Formatter options to 'always' (#6240) 2025-10-23 21:59:32 +02:00
59 changed files with 1540 additions and 2269 deletions

167
.github/copilot-instructions.md vendored Normal file
View 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/`

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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: "../",

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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 секунди), ако уеб куката се првали."
}

View File

@@ -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 প্রতিক্রিয়ায় কীওয়ার্ড অনুসন্ধান করুন। এই অনুসন্ধানটি কেস-সেনসিটিভ।"
}

View File

@@ -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 60180 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"
}

View File

@@ -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 60180 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."
}

View File

@@ -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 60180 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."
}

View File

@@ -306,6 +306,7 @@
"Show Tags": "Show Tags",
"Hide Tags": "Hide Tags",
"Description": "Description",
"descriptionHelpText": "Shown on the internal dashboard. Markdown is allowed and sanitized (preserves spaces and indentation) before display.",
"No monitors available.": "No monitors available.",
"Add one": "Add one",
"No Monitors": "No Monitors",
@@ -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"
}

View File

@@ -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 60180 segundos) si el webhook falla.",
"Maximum Retries": "Máximo de reintentos"
}

View File

@@ -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.": "Linstallation dun 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 dIDs utilisateur",
"supportBaleChatID": "Prend en charge lID 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 dUptime Kuma.",
"descriptionHelpText": "Affiché sur le tableau de bord interne. Le Markdown est autorisé et assaini (les espaces et lindentation sont conservés) avant laffichage."
}

View File

@@ -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 dID 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"
}

View File

@@ -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}]"
}

View File

@@ -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."
}

View File

@@ -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": "მუშავდება"
}

View File

@@ -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": "메세지 템플릿"
}

View File

@@ -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: Przej 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."
}

View File

@@ -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}]"
}

View File

@@ -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": "Автоматический выбор"
}

View File

@@ -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ám 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ám 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 60180 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."
}

View File

@@ -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, MariaDByi 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": "Bota bir mesaj göndererek ve şu URLye giderek chat_idnizi 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"
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,9 +0,0 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"esModuleInterop": false
},
"files": [
"./src/util.ts"
]
}