Compare commits

..

85 Commits

Author SHA1 Message Date
976d9bbece ai: add CLAUDE.md for AI agent context
Some checks failed
Auto Test / auto-test (20, ubuntu-22.04) (push) Failing after 4m24s
Auto Test / auto-test (24, ubuntu-22.04) (push) Failing after 49s
Auto Test / auto-test (25, ubuntu-22.04) (push) Failing after 43s
Auto Test / armv7-simple-test (20) (push) Failing after 1m38s
Auto Test / armv7-simple-test (22) (push) Failing after 48s
Auto Test / check-linters (push) Failing after 41s
autofix.ci / autofix (push) Failing after 44s
CodeQL / Analyze (go) (push) Failing after 48m15s
CodeQL / Analyze (javascript-typescript) (push) Failing after 1m12s
CodeQL / zizmor (push) Failing after 16m14s
Merge Conflict Labeler / Labeling (push) Has been skipped
validate / json-yaml-validate (push) Failing after 57s
validate / validate (push) Failing after 38s
Auto Test / auto-test (20, macos-latest) (push) Has been cancelled
Auto Test / auto-test (20, ubuntu-22.04-arm) (push) Has been cancelled
Auto Test / auto-test (20, windows-latest) (push) Has been cancelled
Auto Test / auto-test (24, macos-latest) (push) Has been cancelled
Auto Test / auto-test (24, ubuntu-22.04-arm) (push) Has been cancelled
Auto Test / auto-test (24, windows-latest) (push) Has been cancelled
Auto Test / e2e-test (push) Has been cancelled
NPM Update / npm-update (push) Has been skipped
Nightly Release / release-nightly (push) Has been skipped
Automatically close stale issues / stale (push) Has been skipped
2026-03-17 08:28:55 -05:00
0xRozier
9b28ddd923 fix: monitor graph gaps for sub-millisecond TCP pings (#7159) 2026-03-16 23:57:04 +01:00
otbutz
aa40ffdf23 fix(database): knex propagate create errors (#7155) 2026-03-16 23:18:05 +08:00
Copilot
59345444e5 chore: Add monthly workflow to build push Docker image (#7146)
Co-authored-by: louislam <1336778+louislam@users.noreply.github.com>
2026-03-14 19:34:24 +08:00
Nic Jansma
448643fcf0 fix: prometheus metrics have two series for a single monitor when that monitor has tags (#7125) 2026-03-10 16:29:35 +00:00
Copilot
0462b6f87b fix: Update Home Assistant notification help text for HA 2024 Services→Actions rename (#7128)
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: CommanderStorm <26258709+CommanderStorm@users.noreply.github.com>
Co-authored-by: Frank Elsinga <frank@elsinga.de>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Manu <4445816+tr4nt0r@users.noreply.github.com>
2026-03-10 12:15:12 +00:00
github-actions[bot]
3752a59f72 chore: update to 2.2.1 (#7126) 2026-03-10 10:24:07 +08:00
github-actions[bot]
4364699f12 chore: Update dependencies (#7120) 2026-03-10 10:14:21 +08:00
Louis Lam
8a46aafa89 chore: Translations Update from Weblate (#7119) 2026-03-10 10:13:32 +08:00
fabianovich
48a2efa38d Translated using Weblate (Dutch)
Currently translated at 97.6% (1472 of 1508 strings)

Co-authored-by: fabianovich <fabianvanacoley@gmail.com>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/nl/
Translation: Uptime Kuma/Uptime Kuma
2026-03-09 10:46:39 +00:00
Cyril59310
37452628e6 Translated using Weblate (French)
Currently translated at 100.0% (1508 of 1508 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
2026-03-09 10:46:39 +00:00
MrEddX
483daa2e15 Translated using Weblate (Bulgarian)
Currently translated at 100.0% (1508 of 1508 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
2026-03-09 10:46:39 +00:00
Teemu Risikko
aedac237d0 fix: isParentActive return type to boolean (#7121) 2026-03-09 11:46:33 +01:00
Louis Lam
1b76499fd3 chore: Translations Update from Weblate (#7099) 2026-03-08 14:40:15 +08:00
Louis Lam
64b32ff133 Added translation using Weblate (English (United Kingdom))
Co-authored-by: Louis Lam <louislam@users.noreply.github.com>
2026-03-08 05:09:38 +00:00
Aluisio
747b8c21fc Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (1508 of 1508 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
2026-03-08 05:09:37 +00:00
riccardo
27132ccc74 Translated using Weblate (Italian)
Currently translated at 79.5% (1193 of 1499 strings)

Co-authored-by: riccardo <mrgianfranco483@gmail.com>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/it/
Translation: Uptime Kuma/Uptime Kuma
2026-03-08 05:09:37 +00:00
Kristaps
3b69b98ff9 Translated using Weblate (Latvian)
Currently translated at 4.8% (72 of 1499 strings)

Co-authored-by: Kristaps <krmuizn@gmail.com>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/lv/
Translation: Uptime Kuma/Uptime Kuma
2026-03-08 05:09:37 +00:00
Aindriú Mac Giolla Eoin
1c97445370 Translated using Weblate (Irish)
Currently translated at 100.0% (1499 of 1499 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
2026-03-08 05:09:37 +00:00
Jozef Gaal
39f9ce8b9c Translated using Weblate (Slovak)
Currently translated at 99.9% (1498 of 1499 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
2026-03-08 05:09:37 +00:00
Maximiliano Simonazzi
ab4a70bfb6 Translated using Weblate (Spanish)
Currently translated at 86.7% (1308 of 1508 strings)

Translated using Weblate (Spanish)

Currently translated at 86.5% (1297 of 1499 strings)

Translated using Weblate (Spanish)

Currently translated at 84.7% (1271 of 1499 strings)

Co-authored-by: Maximiliano Simonazzi <maxisimonazzi@gmail.com>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/es/
Translation: Uptime Kuma/Uptime Kuma
2026-03-08 05:09:37 +00:00
github-actions[bot]
62bfc38c27 chore: Update dependencies (#7100)
Co-authored-by: Louis Lam <louislam@users.noreply.github.com>
2026-03-08 11:28:19 +08:00
riccardo
f45467bd7b fix(ui): fix fluxer input styling; make discord input hidden (#7110) 2026-03-08 03:43:59 +01:00
riccardo
c60210124a feat: fluxer notification provider (#7109) 2026-03-07 19:55:46 +01:00
Louis Lam
c80e3cfb9d fix: revert: remove @aws-sdk, @azure packages (#7101) 2026-03-06 22:21:03 +08:00
Manu
165c5c1134 fix: removal of uptime ratio and avg. response time from prometheus metrics (#6915)
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-03-06 11:51:09 +00:00
Zhong Lufan (钟路帆)
b00f721529 feat: set process.title to uptime-kuma (#7096) 2026-03-05 11:17:31 +01:00
Louis Lam
e63b2034de chore: update to 2.2.0 (#7092) 2026-03-05 09:58:30 +08:00
github-actions[bot]
e6e2808006 Update to 2.2.0 2026-03-05 01:55:01 +00:00
Louis Lam
c9d9790f65 chore: Translations Update from Weblate (#7072) 2026-03-05 09:32:04 +08:00
Helak
2db01249f9 Translated using Weblate (Czech)
Currently translated at 100.0% (1499 of 1499 strings)

Co-authored-by: Helak <adamhavra@seznam.cz>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/cs/
Translation: Uptime Kuma/Uptime Kuma
2026-03-05 01:30:37 +00:00
Virenbar
8a4567f358 Translated using Weblate (Russian)
Currently translated at 100.0% (1499 of 1499 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
2026-03-05 01:30:37 +00:00
Robert Coroianu
3f2ae051bd Translated using Weblate (Romanian)
Currently translated at 69.0% (1035 of 1499 strings)

Co-authored-by: Robert Coroianu <robert.coroianu@gmail.com>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/ro/
Translation: Uptime Kuma/Uptime Kuma
2026-03-05 01:30:37 +00:00
Cyril59310
ca932bd486 Translated using Weblate (French)
Currently translated at 100.0% (1499 of 1499 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
2026-03-05 01:30:37 +00:00
Aluisio
0b018ff557 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (1499 of 1499 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
2026-03-05 01:30:37 +00:00
MrEddX
f739744036 Translated using Weblate (Bulgarian)
Currently translated at 100.0% (1499 of 1499 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
2026-03-05 01:30:37 +00:00
Louis Lam
303a609c05 Merge commit from fork 2026-03-05 09:30:32 +08:00
github-actions[bot]
3aea1dfaaf chore: Update dependencies (#7071)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-03-05 09:10:52 +08:00
Lanin Aleksandr
6f74cd3ce8 feat(proxy): allow socks proxy for the notifications (#7088) 2026-03-04 15:08:31 +01:00
Louis Lam
10180dbd88 chore: generate package-lock.json from scratch to ensure accuracy (#7077) 2026-03-02 10:03:10 +08:00
Louis Lam
9be0f8a081 fix(monitor-list): cannot display monitor name in full width (#7076) 2026-03-02 01:20:10 +00:00
Gabriele C.
2c6dcbb7fa fix: Fix domain validation not allowing for PTR DNS records (#7048) 2026-03-02 00:24:49 +01:00
MayMeow
111e981f73 fix: Remove forced DOWN status in group monitor (#7045) 2026-03-01 18:03:21 +08:00
Rohit Darekar
ce740724d8 fix: preserve Domain Name Expiry Notification setting when editing monitor (#6994)
Co-authored-by: Louis Lam <louislam@users.noreply.github.com>
2026-02-28 23:38:29 +00:00
Louis Lam
075aa61806 fix: Revert "feat(dns): add configurable timeout for DNS monitor" (#7036) 2026-03-01 06:54:18 +08:00
Louis Lam
fef4826e5d fix(monitor-list): tag list do not update (#7069) 2026-03-01 06:52:52 +08:00
Louis Lam
f239d1f212 chore: Translations Update from Weblate (#7007) 2026-03-01 06:46:59 +08:00
Darek
946e52348d Translated using Weblate (Polish)
Currently translated at 94.4% (1417 of 1501 strings)

Co-authored-by: Darek <darek.wach@o2.pl>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/pl/
Translation: Uptime Kuma/Uptime Kuma
2026-02-28 22:19:02 +00:00
Mário Garrido
7de4753290 Translated using Weblate (Portuguese (Portugal))
Currently translated at 31.6% (475 of 1501 strings)

Co-authored-by: Mário Garrido <login@mariogarrido.pt>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/pt_PT/
Translation: Uptime Kuma/Uptime Kuma
2026-02-28 22:19:02 +00:00
Helak
51ce0db785 Translated using Weblate (Czech)
Currently translated at 94.9% (1425 of 1501 strings)

Translated using Weblate (Czech)

Currently translated at 93.9% (1409 of 1500 strings)

Co-authored-by: Helak <adamhavra@seznam.cz>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/cs/
Translation: Uptime Kuma/Uptime Kuma
2026-02-28 22:19:02 +00:00
Virenbar
649ef4795b Translated using Weblate (Russian)
Currently translated at 100.0% (1500 of 1500 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (1482 of 1482 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
2026-02-28 22:19:02 +00:00
Florian Berger
1994a412fc Translated using Weblate (German)
Currently translated at 100.0% (1482 of 1482 strings)

Co-authored-by: Florian Berger <dev@florian-berger.info>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/de/
Translation: Uptime Kuma/Uptime Kuma
2026-02-28 22:19:02 +00:00
Ivan Bratović
4ee92a54bd Translated using Weblate (Croatian)
Currently translated at 100.0% (1479 of 1479 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
2026-02-28 22:19:02 +00:00
Belen
df5ac6d1c6 Translated using Weblate (Spanish)
Currently translated at 85.5% (1265 of 1479 strings)

Translated using Weblate (Spanish)

Currently translated at 84.5% (1250 of 1479 strings)

Co-authored-by: Belen <Belentr@hotmail.es>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/es/
Translation: Uptime Kuma/Uptime Kuma
2026-02-28 22:19:02 +00:00
Yoswaris Lawpaiboon
4d72e241f6 Translated using Weblate (Thai)
Currently translated at 66.5% (984 of 1479 strings)

Co-authored-by: Yoswaris Lawpaiboon <konglha19@outlook.co.th>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/th/
Translation: Uptime Kuma/Uptime Kuma
2026-02-28 22:19:02 +00:00
梦曦·花已落
efb9a79254 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (1479 of 1479 strings)

Co-authored-by: 梦曦·花已落 <qq625924077@gmail.com>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/zh_Hans/
Translation: Uptime Kuma/Uptime Kuma
2026-02-28 22:19:02 +00:00
Jozef Gaal
350839fce0 Translated using Weblate (Slovak)
Currently translated at 99.9% (1478 of 1479 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
2026-02-28 22:19:02 +00:00
AnnAngela
cdc90a0647 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (1501 of 1501 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (1500 of 1500 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (1479 of 1479 strings)

Co-authored-by: AnnAngela <naganjue@vip.qq.com>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/zh_Hans/
Translation: Uptime Kuma/Uptime Kuma
2026-02-28 22:19:02 +00:00
Abc's Noob
f09b311b4e Translated using Weblate (Vietnamese)
Currently translated at 39.7% (588 of 1479 strings)

Co-authored-by: Abc's Noob <abcsnoob@duck.com>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/vi/
Translation: Uptime Kuma/Uptime Kuma
2026-02-28 22:19:02 +00:00
Anton Palmqvist
d552a5dbaf Translated using Weblate (Swedish)
Currently translated at 82.0% (1213 of 1479 strings)

Co-authored-by: Anton Palmqvist <apalmqvist@pm.me>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/sv/
Translation: Uptime Kuma/Uptime Kuma
2026-02-28 22:19:02 +00:00
Aluisio
5041834cd4 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (1501 of 1501 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (1500 of 1500 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (1482 of 1482 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (1479 of 1479 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
2026-02-28 22:19:02 +00:00
Cyril59310
fae38503bd Translated using Weblate (French)
Currently translated at 100.0% (1479 of 1479 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
2026-02-28 22:19:02 +00:00
MrEddX
99ced19229 Translated using Weblate (Bulgarian)
Currently translated at 100.0% (1501 of 1501 strings)

Translated using Weblate (Bulgarian)

Currently translated at 100.0% (1500 of 1500 strings)

Translated using Weblate (Bulgarian)

Currently translated at 100.0% (1482 of 1482 strings)

Translated using Weblate (Bulgarian)

Currently translated at 100.0% (1479 of 1479 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
2026-02-28 22:19:02 +00:00
Aindriú Mac Giolla Eoin
c0795d591e Translated using Weblate (Irish)
Currently translated at 100.0% (1477 of 1477 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
2026-02-28 22:19:02 +00:00
Arion2000
78983bf5e0 Translated using Weblate (German)
Currently translated at 100.0% (1482 of 1482 strings)

Translated using Weblate (German)

Currently translated at 100.0% (1479 of 1479 strings)

Translated using Weblate (German)

Currently translated at 100.0% (1477 of 1477 strings)

Co-authored-by: Arion2000 <github@arion2000.xyz>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/de/
Translation: Uptime Kuma/Uptime Kuma
2026-02-28 22:19:02 +00:00
github-actions[bot]
d118d64271 chore: Update dependencies (#6991)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-03-01 06:18:56 +08:00
Louis Lam
5c81277702 chore: improve changelog generator (#7058) 2026-03-01 05:41:39 +08:00
Radu Lucuț
bdcbd4c886 fix(globalping): retry creating the measurement on status 500 (#7056) 2026-02-26 18:07:32 +00:00
Cassandra
174c63d479 feat: structured logging (JSON) (#5179)
Co-authored-by: Frank Elsinga <frank@elsinga.de>
Co-authored-by: Louis Lam <louislam@users.noreply.github.com>
2026-02-26 17:02:58 +00:00
Radu Lucuț
b36a8b035b fix(globalping): show error message when using multiple locations (#7055) 2026-02-26 17:34:00 +01:00
WaMessenger
86b86fae55 feat: add whatsApp (360messenger) notification provider (#7046)
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-02-25 10:41:59 +00:00
Copilot
953d97fd2e fix: Setting Status Page Analytics Type to "None" fails with SQLite constraint violation (#7043)
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: CommanderStorm <26258709+CommanderStorm@users.noreply.github.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-02-25 05:25:26 +00:00
mixelburg
0ce2ebb31e feat(dns): add configurable timeout for DNS monitor (#6990)
Co-authored-by: Maks Pikov <mixelburg@users.noreply.github.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Frank Elsinga <frank@elsinga.de>
2026-02-23 21:17:22 +01:00
Louis Lam
bd68103ade fix(edit-monitor): several issues in the edit page (#7011) 2026-02-23 23:43:44 +08:00
mixelburg
1de276006c fix: show actual bind address in startup logs (#6999)
Co-authored-by: Maks Pikov <mixelburg@users.noreply.github.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-02-23 23:04:48 +08:00
Louis Lam
49f2633c9b chore: remove @aws-sdk, @azure packages (#7032) 2026-02-22 16:34:09 +00:00
Louis Lam
37dc8f6afd fix(edit-monitor): url validation is incorrect (#7010) 2026-02-22 16:38:28 +08:00
Louis Lam
c817c008d3 Revert "chore: update autofix.yml to be triggered by validate.yml first" (#7027) 2026-02-22 10:30:51 +08:00
Louis Lam
e537a4fb73 chore: update autofix.yml to be triggered by validate.yml first (#7026) 2026-02-22 10:17:45 +08:00
Louis Lam
5cd23d237c fix: Uptime Kuma is not able to start on older version of Node.js 20 (~20.17.0) (#7019) 2026-02-22 09:38:50 +08:00
Louis Lam
a1172ab4b3 chore: follow up #7013 (#7015) 2026-02-22 03:37:46 +08:00
Louis Lam
e0c0eaea66 chore: add PR description template check workflow (#7013) 2026-02-22 03:30:26 +08:00
Karthikeya chanda
019b4b7503 feat: allow templating in the Signal notificaiton provider (#6989)
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Frank Elsinga <frank@elsinga.de>
2026-02-19 20:30:49 +01:00
mixelburg
c90a7f30e1 fix: show certificate expiry on status page for TCP monitors (#6984)
Co-authored-by: Maks Pikov <mixelburg@users.noreply.github.com>
2026-02-19 12:53:50 +00:00
Louis Lam
41e75ddd6e refactor: improve SQLite multiple connections (#6979) 2026-02-19 13:58:55 +08:00
85 changed files with 4833 additions and 2058 deletions

View File

@@ -10,6 +10,7 @@ module.exports = {
extends: [
"eslint:recommended",
"plugin:vue/vue3-recommended",
"plugin:vue-scoped-css/vue3-recommended",
"plugin:jsdoc/recommended-error",
"prettier", // Disables ESLint formatting rules that conflict with Prettier
],
@@ -42,6 +43,7 @@ module.exports = {
"vue/require-component-is": "off", // not allow is="style" https://github.com/vuejs/eslint-plugin-vue/issues/462#issuecomment-430234675
"vue/attribute-hyphenation": "off", // This change noNL to "no-n-l" unexpectedly
"vue/multi-word-component-names": "off",
"vue-scoped-css/no-unused-selector": "warn",
curly: "error",
"no-var": "error",
"no-throw-literal": "error",

49
.github/workflows/build-docker-push.yml vendored Normal file
View File

@@ -0,0 +1,49 @@
name: Build Docker Push Image
on:
schedule:
# Runs at 2:00 AM UTC on the 1st of every month
- cron: "0 2 1 * *"
workflow_dispatch: # Allow manual trigger
permissions: {}
jobs:
build-docker-push:
# Only run on the original repository, not on forks
if: github.repository == 'louislam/uptime-kuma'
runs-on: ubuntu-latest
timeout-minutes: 120
permissions:
contents: read
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with: { persist-credentials: false }
- name: Set up QEMU
uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3.7.0
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@988b5a0280414f521da01fcc63a27aeeb4b104db # v3.6.1
- name: Login to Docker Hub
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Use Node.js 20
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
with:
node-version: 20
- name: Set up Go
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
- name: Install cross-env
run: npm install -g cross-env
- name: Build and push Docker image
working-directory: extra/uptime-kuma-push
run: npm run build-docker

View File

@@ -30,8 +30,10 @@ jobs:
with:
node-version: 20
- name: Run npm update
run: npm update --package-lock-only
- name: Generate lockfile from scratch
run: |
rm -f package-lock.json
npm install --package-lock-only
- name: Check if there are changes
id: check_changes

View File

@@ -0,0 +1,54 @@
name: "PR description template check"
on: # zizmor: ignore[dangerous-triggers]
pull_request_target:
types: [opened, reopened]
permissions:
pull-requests: write
issues: write
contents: read
jobs:
check-pr-description:
name: Check PR description and close if missing template phrase
runs-on: ubuntu-latest
permissions:
pull-requests: write
issues: write
steps:
- name: Check PR description
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
with:
script: |
const pr = context.payload.pull_request;
const body = (pr && pr.body) ? pr.body : "";
const requiredPhrase = "avoid unnecessary back and forth";
const exclude = ["UptimeKumaBot", "Copilot", "copilot-swe-agent"];
const excludeLower = exclude.map((e) => e.toLowerCase());
const author = pr?.user?.login || "";
// If author is in exclude list, skip
if (author && excludeLower.includes(author.toLowerCase())) {
core.info(`PR #${pr.number} opened by excluded user '${author}', skipping template check.`);
return;
}
if (!body || !body.toLowerCase().includes(requiredPhrase.toLowerCase())) {
const owner = context.repo.owner;
const repo = context.repo.repo;
const number = pr.number;
const commentBody = `Hello! This pull request does not follow the repository's PR template and is being closed automatically.`;
// Post comment
await github.rest.issues.createComment({ owner, repo, issue_number: number, body: commentBody });
// Close
await github.rest.pulls.update({ owner, repo, pull_number: number, state: "closed" });
core.info(`Closed PR #${number} because required phrase was not present.`);
} else {
core.info("PR description contains required phrase; no action taken.");
}

View File

@@ -35,13 +35,16 @@ jobs:
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with: { persist-credentials: false }
- name: Use Node.js 20
- name: Use Node.js 25
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
with:
node-version: 20
node-version: 25
- name: Validate language JSON files
run: node ./extra/check-lang-json.js
- name: Validate knex migrations filename
run: node ./extra/check-knex-filenames.mjs
- name: Validate package.json
run: node ./extra/check-package-json.mjs

26
CLAUDE.md Normal file
View File

@@ -0,0 +1,26 @@
# CLAUDE.md
This is `guardrail` — part of BlackRoad OS.
> GuardRail — BlackRoad uptime monitoring. Forked from Uptime Kuma.
## Owner
BlackRoad OS, Inc. — Proprietary. All rights reserved.
## AI Instructions
- Part of BlackRoad ecosystem (615+ repos, 15 orgs)
- Primary git: RoadCode (Gitea). GitHub is mirror.
- All code proprietary unless marked otherwise
- Brand: black bg, white text, gradient accents
- Fonts: Space Grotesk, Inter, JetBrains Mono
- Language: JavaScript
## Memory System
- Check codex: `memory-codex.sh search "<problem>"`
- Log actions: `memory-system.sh log <action> <entity> "<details>"`
- Broadcast: `memory-til-broadcast.sh broadcast <cat> "<learning>"`
## Collaboration
- Register: `memory-collaboration.sh register`
- Claim: `memory-collaboration.sh claim "<task>"`
- Board: `memory-collaboration.sh board`

View File

@@ -0,0 +1,17 @@
import packageJSON from "../package.json" with { type: "json" };
let hasError = false;
for (const dep in packageJSON.dependencies) {
const semver = packageJSON.dependencies[dep];
if (semver.startsWith("^")) {
console.error(`Dependency ${dep} has a caret (^) in its version. Please change it to (~)`);
hasError = true;
}
}
if (hasError) {
process.exit(1);
} else {
console.log("All dependencies are valid.");
}

View File

@@ -4,29 +4,76 @@
import * as childProcess from "child_process";
const ignoreList = ["louislam", "CommanderStorm", "UptimeKumaBot", "weblate", "Copilot", "autofix-ci[bot]", "app/copilot-swe-agent", "app/github-actions", "github-actions[bot]"];
const ignoreList = [
"louislam",
"CommanderStorm",
"UptimeKumaBot",
"weblate",
"Copilot",
"autofix-ci[bot]",
"app/copilot-swe-agent",
"app/github-actions",
"github-actions[bot]",
];
const mergeList = ["chore: Translations Update from Weblate", "chore: Update dependencies"];
const template = `
const outputFormat = JSON.stringify({
improvements: [123, 456],
newFeatures: [789],
bugFixes: [101, 112],
securityFixes: [131, 415],
translationContributions: [161, 718],
others: [192, 21],
});
LLM Task: Please help to put above PRs into the following sections based on their content. If a PR fits multiple sections, choose the most relevant one. If a PR doesn't fit any section, place it in "Others". If there are grammatical errors in the PR titles, please correct them. Don't change the PR numbers and authors, and keep the format. Output as markdown file format.
const prompt = `Input Data:
\`\`\`json
{{ input }}
\`\`\`
Changelog:
LLM Task:
- Output a one-line JSON object in the following format:
{{ outputFormat }}
- Empty arrays included if there are no items for that category.
- Exclude reverted pull requests.
- "fix: " type pull requests should be categorized as "bugFixes".
- "chore: " type pull requests should be categorized as "others"
- "feat: " type pull requests should be categorized as "newFeatures" or "improvements" based on the content of the title, you should determine it.
- "refactor: " type pull requests should be categorized as "improvements".
`.replace("{{ outputFormat }}", outputFormat);
### 🆕 New Features
### 💇‍♀️ Improvements
### 🐞 Bug Fixes
### ⬆️ Security Fixes
### 🦎 Translation Contributions
### Others
- Other small changes, code refactoring and comment/doc updates in this repo:
`;
const categoryList = {
// In case the LLM cannot categorize some items
uncategorized: {
title: "Uncategorized",
items: [],
},
newFeatures: {
title: "🆕 New Features",
items: [],
},
improvements: {
title: "💇‍♀️ Improvements",
items: [],
},
bugFixes: {
title: "🐞 Bug Fixes",
items: [],
},
securityFixes: {
title: "⬆️ Security Fixes",
items: [],
},
translationContributions: {
title: "🦎 Translation Contributions",
items: [],
},
others: {
title: "Others",
items: [],
},
};
if (import.meta.main) {
await main();
@@ -38,25 +85,40 @@ if (import.meta.main) {
*/
async function main() {
const previousVersion = process.argv[2];
const action = process.argv[3];
const categorizedMap = process.argv[4] ? JSON.parse(process.argv[4]) : null;
if (!previousVersion) {
console.error("Please provide the previous version as the first argument.");
process.exit(1);
if (action === "generate") {
console.log(`Generating changelog since version ${previousVersion}...`);
console.log(await generateChangelog(previousVersion, categorizedMap));
} else {
if (!previousVersion) {
console.error("Please provide the previous version as the first argument.");
process.exit(1);
}
console.log(await getPrompt(previousVersion));
}
}
console.log(`Generating changelog since version ${previousVersion}...`);
console.log(await generateChangelog(previousVersion));
/**
* Get Prompt for LLM
* @param {string} previousVersion Previous Version Tag
* @returns {Promise<string>} Prompt for LLM
*/
export async function getPrompt(previousVersion) {
const input = JSON.stringify(await getPullRequestList(previousVersion, true));
return prompt.replace("{{ input }}", input);
}
/**
* Generate Changelog
* @param {string} previousVersion Previous Version Tag
* @param {object} categorizedMap It should be generated by the LLM based on the prompt
* @returns {Promise<string>} Changelog Content
*/
export async function generateChangelog(previousVersion) {
export async function generateChangelog(previousVersion, categorizedMap) {
const prList = await getPullRequestList(previousVersion);
const list = [];
let content = "";
let i = 1;
for (const pr of prList) {
@@ -98,20 +160,45 @@ export async function generateChangelog(previousVersion) {
authorPart = `(Thanks ${authorPart})`;
}
content += `- ${prPart} ${item.title} ${authorPart}\n`;
const line = `- ${prPart} ${item.title} ${authorPart}`;
// Determine the category of the item, based on the title and the categorizedMap
let category = "uncategorized";
let prNumber = item.numbers[0];
for (const cat in categorizedMap) {
if (categorizedMap[cat].includes(prNumber)) {
category = cat;
break;
}
}
categoryList[category].items.push(line);
}
return content + "\n" + template;
// Generate markdown
let content = "";
for (const cat in categoryList) {
content += `### ${categoryList[cat].title}\n`;
for (const item of categoryList[cat].items) {
content += `${item}\n`;
}
content += `\n`;
}
return content;
}
/**
* @param {string} previousVersion Previous Version Tag
* @param {boolean} removeAuthor Whether to strip the author field from the returned PR list
* @returns {Promise<object>} List of Pull Requests merged since previousVersion
*/
async function getPullRequestList(previousVersion) {
// Get the date of previousVersion in YYYY-MM-DD format from git
async function getPullRequestList(previousVersion, removeAuthor = false) {
// Get the date of previousVersion in iso8601-strict format (2026-02-19T13:34:03+08:00) from git
const previousVersionDate = childProcess
.execSync(`git log -1 --format=%cd --date=short ${previousVersion}`)
.execSync(`git log -1 --format=%cd --date=iso8601-strict ${previousVersion}`)
.toString()
.trim();
@@ -150,7 +237,15 @@ async function getPullRequestList(previousVersion) {
throw new Error(`gh command failed with status ${ghProcess.status}: ${ghProcess.stderr}`);
}
return JSON.parse(ghProcess.stdout);
const obj = JSON.parse(ghProcess.stdout);
if (removeAuthor) {
for (const pr of obj) {
delete pr.author;
}
}
return obj;
}
/**

View File

@@ -1,7 +1,7 @@
import "dotenv/config";
import * as childProcess from "child_process";
import semver from "semver";
import { generateChangelog } from "../generate-changelog.mjs";
import { getPrompt } from "../generate-changelog.mjs";
import fs from "fs";
import tar from "tar";
@@ -308,15 +308,15 @@ export async function createDistTarGz() {
* @returns {Promise<void>}
*/
export async function createReleasePR(version, previousVersion, dryRun, branchName = "release", githubRunId = null) {
const changelog = await generateChangelog(previousVersion);
const prompt = await getPrompt(previousVersion);
const title = dryRun ? `chore: update to ${version} (dry run)` : `chore: update to ${version}`;
// Build the artifact link - use direct run link if available, otherwise link to workflow file
const artifactLink = githubRunId
const artifactLink = githubRunId
? `https://github.com/louislam/uptime-kuma/actions/runs/${githubRunId}/workflow`
: `https://github.com/louislam/uptime-kuma/actions/workflows/beta-release.yml`;
const body = `## Release ${version}
This PR prepares the release for version ${version}.
@@ -330,10 +330,16 @@ This PR prepares the release for version ${version}.
- [ ] (Beta only) Set prerelease
- [ ] Publish the release note on GitHub.
### Changelog
### Ask LLM to categorize the changelog
\`\`\`md
${changelog}
${prompt}
\`\`\`
Run the following command to generate the changelog with the categorized map from LLM:
\`\`\`bash
npm run generate-changelog ${previousVersion} generate 'JSON_MAPPING_BY_LLM_HERE'
\`\`\`
### Release Artifacts
@@ -341,7 +347,19 @@ The \`dist.tar.gz\` archive will be available as an artifact in the workflow run
`;
// Create the PR using gh CLI
const args = ["pr", "create", "--title", title, "--body", body, "--base", "master", "--head", branchName, "--draft"];
const args = [
"pr",
"create",
"--title",
title,
"--body",
body,
"--base",
"master",
"--head",
branchName,
"--draft",
];
console.log(`Creating draft PR: ${title}`);

View File

@@ -1,3 +0,0 @@
package-lock.json
test.js
languages/

View File

@@ -1,105 +0,0 @@
// Need to use ES6 to read language files
import fs from "fs";
import util from "util";
/**
* Copy across the required language files
* Creates a local directory (./languages) and copies the required files
* into it.
* @param {string} langCode Code of language to update. A file will be
* created with this code if one does not already exist
* @param {string} baseLang The second base language file to copy. This
* will be ignored if set to "en" as en.js is copied by default
* @returns {void}
*/
function copyFiles(langCode, baseLang) {
if (fs.existsSync("./languages")) {
fs.rmSync("./languages", {
recursive: true,
force: true,
});
}
fs.mkdirSync("./languages");
if (!fs.existsSync(`../../src/languages/${langCode}.js`)) {
fs.closeSync(fs.openSync(`./languages/${langCode}.js`, "a"));
} else {
fs.copyFileSync(`../../src/languages/${langCode}.js`, `./languages/${langCode}.js`);
}
fs.copyFileSync("../../src/languages/en.js", "./languages/en.js");
if (baseLang !== "en") {
fs.copyFileSync(`../../src/languages/${baseLang}.js`, `./languages/${baseLang}.js`);
}
}
/**
* Update the specified language file
* @param {string} langCode Language code to update
* @param {string} baseLangCode Second language to copy keys from
* @returns {void}
*/
async function updateLanguage(langCode, baseLangCode) {
const en = (await import("./languages/en.js")).default;
const baseLang = (await import(`./languages/${baseLangCode}.js`)).default;
let file = langCode + ".js";
console.log("Processing " + file);
const lang = await import("./languages/" + file);
let obj;
if (lang.default) {
obj = lang.default;
} else {
console.log("Empty file");
obj = {
languageName: "<Your Language name in your language (not in English)>",
};
}
// En first
for (const key in en) {
if (!obj[key]) {
obj[key] = en[key];
}
}
if (baseLang !== en) {
// Base second
for (const key in baseLang) {
if (!obj[key]) {
obj[key] = key;
}
}
}
const code =
"export default " +
util.inspect(obj, {
depth: null,
});
fs.writeFileSync(`../../src/languages/${file}`, code);
}
// Get command line arguments
const baseLangCode = process.env.npm_config_baselang || "en";
const langCode = process.env.npm_config_language;
// We need the file to edit
if (langCode == null) {
throw new Error("Argument --language=<code> must be provided");
}
console.log("Base Lang: " + baseLangCode);
console.log("Updating: " + langCode);
copyFiles(langCode, baseLangCode);
await updateLanguage(langCode, baseLangCode);
fs.rmSync("./languages", {
recursive: true,
force: true,
});
console.log("Done. Fixing formatting by ESLint...");

View File

@@ -1,12 +0,0 @@
{
"name": "update-language-files",
"type": "module",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC"
}

2998
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "uptime-kuma",
"version": "2.1.3",
"version": "2.2.1",
"license": "MIT",
"repository": {
"type": "git",
@@ -11,7 +11,7 @@
},
"scripts": {
"lint:js": "eslint --ext \".js,.vue\" --ignore-path .gitignore .",
"lint:js-prod": "npm run lint:js -- --max-warnings 0",
"lint:js-prod": "npm run lint:js",
"lint-fix:js": "eslint --ext \".js,.vue\" --fix --ignore-path .gitignore .",
"lint:style": "stylelint \"**/*.{vue,css,scss}\" --ignore-path .gitignore",
"lint-fix:style": "stylelint \"**/*.{vue,css,scss}\" --fix --ignore-path .gitignore",
@@ -44,7 +44,7 @@
"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",
"upload-artifacts": "node extra/release/upload-artifacts.mjs",
"upload-artifacts-beta": "node extra/release/upload-artifacts-beta.mjs",
"setup": "git checkout 2.1.3 && npm ci --omit dev --no-audit && npm run download-dist",
"setup": "git checkout 2.2.1 && 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",
@@ -54,7 +54,6 @@
"simple-mongo": "docker run --rm -p 27017:27017 mongo",
"simple-postgres": "docker run --rm -p 5432:5432 -e POSTGRES_PASSWORD=postgres postgres",
"simple-mariadb": "docker run --rm -p 3306:3306 -e MYSQL_ROOT_PASSWORD=mariadb# mariadb",
"update-language-files": "cd extra/update-language-files && node index.js && cross-env-shell eslint ../../src/languages/$npm_config_language.js --fix",
"release-final": "node ./extra/release/final.mjs",
"release-beta": "node ./extra/release/beta.mjs",
"release-nightly": "node ./extra/release/nightly.mjs",
@@ -73,153 +72,152 @@
"@grpc/grpc-js": "~1.8.22",
"@louislam/ping": "~0.4.4-mod.1",
"@louislam/sqlite3": "15.1.6",
"@vvo/tzdb": "^6.125.0",
"@vvo/tzdb": "~6.198.0",
"args-parser": "~1.3.0",
"axios": "~0.30.0",
"axios": "~0.30.3",
"badge-maker": "~3.3.1",
"bcryptjs": "~2.4.3",
"chardet": "~1.4.0",
"check-password-strength": "^2.0.5",
"cheerio": "~1.0.0-rc.12",
"check-password-strength": "~2.0.10",
"cheerio": "~1.0.0",
"chroma-js": "~2.4.2",
"command-exists": "~1.2.9",
"compare-versions": "~3.6.0",
"compression": "~1.8.1",
"country-flag-emoji-polyfill": "^0.1.8",
"croner": "~8.1.0",
"dayjs": "~1.11.5",
"dev-null": "^0.1.1",
"country-flag-emoji-polyfill": "~0.1.8",
"croner": "~8.1.2",
"dayjs": "~1.11.19",
"dev-null": "~0.1.1",
"dotenv": "~16.0.3",
"express": "~4.22.1",
"express-basic-auth": "~1.2.1",
"express-static-gzip": "~2.1.7",
"feed": "^4.2.2",
"form-data": "~4.0.0",
"gamedig": "^5.0.1",
"globalping": "^0.2.0",
"html-escaper": "^3.0.3",
"express-static-gzip": "~2.1.8",
"feed": "~4.2.2",
"form-data": "~4.0.5",
"gamedig": "~5.3.2",
"globalping": "~0.2.0",
"html-escaper": "~3.0.3",
"http-cookie-agent": "~5.0.4",
"http-graceful-shutdown": "~3.1.7",
"http-graceful-shutdown": "~3.1.15",
"http-proxy-agent": "~7.0.2",
"https-proxy-agent": "~7.0.6",
"iconv-lite": "~0.6.3",
"is-url": "^1.2.4",
"isomorphic-ws": "^5.0.0",
"is-url": "~1.2.4",
"isomorphic-ws": "~5.0.0",
"jsesc": "~3.0.2",
"jsonata": "^2.0.3",
"jsonwebtoken": "~9.0.0",
"jsonata": "~2.1.0",
"jsonwebtoken": "~9.0.3",
"jwt-decode": "~3.1.2",
"kafkajs": "^2.2.4",
"kafkajs": "~2.2.4",
"knex": "~3.1.0",
"limiter": "~2.1.0",
"liquidjs": "^10.7.0",
"marked": "^14.0.0",
"liquidjs": "~10.25.0",
"marked": "~14.1.4",
"mitt": "~3.0.1",
"mongodb": "~4.17.1",
"mqtt": "~4.3.7",
"mongodb": "~4.17.2",
"mqtt": "~4.3.8",
"mssql": "~12.0.0",
"mysql2": "~3.11.3",
"nanoid": "~3.3.4",
"net-snmp": "^3.11.2",
"node-cloudflared-tunnel": "~1.0.9",
"mysql2": "~3.11.5",
"nanoid": "~3.3.11",
"net-snmp": "~3.26.1",
"node-cloudflared-tunnel": "~1.0.10",
"node-radius-utils": "~1.2.0",
"nodemailer": "~7.0.12",
"nostr-tools": "^2.17.0",
"nodemailer": "~7.0.13",
"nostr-tools": "~2.20.0",
"notp": "~2.0.3",
"openid-client": "^5.4.2",
"openid-client": "~5.7.1",
"password-hash": "~1.2.2",
"pg": "~8.11.3",
"pg-connection-string": "~2.6.2",
"pg": "~8.11.6",
"pg-connection-string": "~2.6.4",
"playwright-core": "~1.39.0",
"prom-client": "~13.2.0",
"prometheus-api-metrics": "~3.2.1",
"prometheus-api-metrics": "~3.2.2",
"promisify-child-process": "~4.1.2",
"protobufjs": "~7.2.4",
"qs": "~6.14.1",
"protobufjs": "~7.2.6",
"qs": "~6.14.2",
"radius": "~1.1.4",
"redbean-node": "~0.3.0",
"redbean-node": "~0.3.3",
"redis": "~5.9.0",
"semver": "~7.5.4",
"socket.io": "~4.8.0",
"socket.io-client": "~4.8.0",
"socket.io": "~4.8.3",
"socket.io-client": "~4.8.3",
"socks-proxy-agent": "~8.0.5",
"sqlstring": "~2.3.3",
"tar": "~6.2.1",
"tcp-ping": "~0.1.1",
"thirty-two": "~1.0.2",
"tldts": "^7.0.19",
"tough-cookie": "~4.1.3",
"validator": "^13.15.26",
"web-push": "^3.6.7",
"ws": "^8.13.0"
"tldts": "~7.0.23",
"tough-cookie": "~4.1.4",
"validator": "~13.15.26",
"web-push": "~3.6.7",
"ws": "~8.19.0"
},
"devDependencies": {
"@actions/github": "~6.0.0",
"@actions/github": "~6.0.1",
"@fortawesome/fontawesome-svg-core": "~1.2.36",
"@fortawesome/free-regular-svg-icons": "~5.15.4",
"@fortawesome/free-solid-svg-icons": "~5.15.4",
"@fortawesome/vue-fontawesome": "~3.1.3",
"@playwright/test": "~1.39.0",
"@popperjs/core": "~2.10.2",
"@testcontainers/hivemq": "^10.13.1",
"@testcontainers/mariadb": "^10.13.0",
"@testcontainers/hivemq": "^10.28.0",
"@testcontainers/mariadb": "^10.28.0",
"@testcontainers/mssqlserver": "^10.28.0",
"@testcontainers/mysql": "^11.11.0",
"@testcontainers/postgresql": "^11.9.0",
"@testcontainers/rabbitmq": "^10.13.2",
"@types/bootstrap": "~5.1.9",
"@types/node": "^20.8.6",
"@testcontainers/mysql": "^11.12.0",
"@testcontainers/postgresql": "^11.12.0",
"@testcontainers/rabbitmq": "^10.28.0",
"@types/bootstrap": "~5.1.13",
"@types/node": "^20.19.33",
"@types/web-push": "^3.6.4",
"@typescript-eslint/eslint-plugin": "^6.7.5",
"@typescript-eslint/parser": "^6.7.5",
"@vitejs/plugin-vue": "~5.0.1",
"@vue/compiler-sfc": "~3.4.2",
"@typescript-eslint/eslint-plugin": "^6.21.0",
"@typescript-eslint/parser": "^6.21.0",
"@vitejs/plugin-vue": "~5.0.5",
"@vue/compiler-sfc": "~3.4.38",
"@vuepic/vue-datepicker": "~3.4.8",
"aedes": "^0.46.3",
"aedes": "~1.0.0",
"bootstrap": "5.1.3",
"chart.js": "~4.2.1",
"chartjs-adapter-dayjs-4": "~1.0.4",
"concurrently": "^7.1.0",
"concurrently": "^7.6.0",
"core-js": "~3.26.1",
"cronstrue": "~2.24.0",
"cross-env": "~7.0.3",
"delay": "^5.0.0",
"dns2": "~2.0.1",
"dompurify": "~3.2.4",
"dns2": "~2.0.5",
"dompurify": "~3.3.2",
"eslint": "~8.14.0",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-jsdoc": "~46.4.6",
"eslint-plugin-vue": "~8.7.1",
"eslint-plugin-vue-scoped-css": "~2.7.2",
"favico.js": "~0.3.10",
"get-port-please": "^3.1.1",
"node-ssh": "~13.1.0",
"postcss-html": "~1.8.1",
"postcss-rtlcss": "~5.7.1",
"postcss-scss": "~4.0.4",
"prettier": "^3.7.4",
"postcss-scss": "~4.0.9",
"prettier": "^3.8.1",
"prismjs": "~1.30.0",
"qrcode": "~1.5.0",
"rollup-plugin-visualizer": "^5.6.0",
"qrcode": "~1.5.4",
"rollup-plugin-visualizer": "^5.14.0",
"sass": "~1.42.1",
"stylelint": "^15.10.1",
"stylelint": "^15.11.0",
"stylelint-config-prettier": "^9.0.5",
"stylelint-config-standard": "~25.0.0",
"terser": "~5.15.0",
"terser": "~5.15.1",
"test": "~3.3.0",
"testcontainers": "^11.5.0",
"testcontainers": "^11.12.0",
"typescript": "~4.4.4",
"v-pagination-3": "~0.1.7",
"vite": "~5.4.15",
"vite": "~5.4.21",
"vite-plugin-compression": "^0.5.1",
"vue": "~3.5.26",
"vue": "~3.5.28",
"vue-chartjs": "~5.2.0",
"vue-confirm-dialog": "~1.0.2",
"vue-contenteditable": "~3.0.4",
"vue-i18n": "~11.2.8",
"vue-image-crop-upload": "~3.0.3",
"vue-multiselect": "~3.0.0-alpha.2",
"vue-multiselect": "~3.0.0",
"vue-prism-editor": "~2.0.0-alpha.2",
"vue-qrcode": "~1.0.0",
"vue-qrcode": "~1.0.1",
"vue-router": "~4.2.5",
"vue-toastification": "~2.0.0-rc.5",
"vuedraggable": "~4.1.0",

View File

@@ -1,7 +1,6 @@
const basicAuth = require("express-basic-auth");
const passwordHash = require("./password-hash");
const { R } = require("redbean-node");
const { setting } = require("./util-server");
const { log } = require("../src/util");
const { loginRateLimiter, apiRateLimiter } = require("./rate-limiter");
const { Settings } = require("./settings");
@@ -137,7 +136,7 @@ exports.basicAuth = async function (req, res, next) {
challenge: true,
});
const disabledAuth = await setting("disableAuth");
const disabledAuth = await Settings.get("disableAuth");
if (!disabledAuth) {
middleware(req, res, next);

View File

@@ -271,11 +271,16 @@ class Database {
},
useNullAsDefault: true,
pool: {
min: 1,
max: 1,
idleTimeoutMillis: 120 * 1000,
propagateCreateError: false,
// SQLite is actually multiple connections for WAL mode, so we can set it to a higher number.
// See: https://github.com/knex/knex/issues/3176#issuecomment-3389054899
min: 0,
max: 20,
acquireTimeoutMillis: acquireConnectionTimeout,
afterCreate: (rawConn, done) => {
this.initSQLite(rawConn, testMode)
.then(() => done(undefined, rawConn))
.catch((err) => done(err, rawConn));
},
},
};
} else if (dbConfig.type === "mariadb") {
@@ -380,40 +385,45 @@ class Database {
}
if (dbConfig.type === "sqlite") {
await this.initSQLite(testMode, noLog);
if (!noLog) {
log.debug("db", "SQLite config:");
log.debug("db", await R.getAll("PRAGMA journal_mode"));
log.debug("db", await R.getAll("PRAGMA cache_size"));
log.debug("db", "SQLite Version: " + (await R.getCell("SELECT sqlite_version()")));
}
} else if (dbConfig.type.endsWith("mariadb")) {
await this.initMariaDB();
}
}
/**
@param {boolean} testMode Should the connection be started in test mode?
@param {boolean} noLog Should logs not be output?
@returns {Promise<void>}
* Initialize SQLite for each connection
* @param {any} rawConn The raw node-sqlite3 Database object
* @param {boolean} testMode Should the connection be started in test mode?
* @returns {Promise<void>}
*/
static async initSQLite(testMode, noLog) {
await R.exec("PRAGMA foreign_keys = ON");
static async initSQLite(rawConn, testMode) {
// Since rawConn.run is callback based, in order to avoid callback hell, wrap it in a promise
const asyncRun = (sql) => {
return new Promise((resolve, reject) => rawConn.run(sql, (err) => (err ? reject(err) : resolve())));
};
if (testMode) {
// Change to MEMORY
await R.exec("PRAGMA journal_mode = MEMORY");
await asyncRun("PRAGMA journal_mode = MEMORY");
} else {
// Change to WAL
await R.exec("PRAGMA journal_mode = WAL");
await asyncRun("PRAGMA journal_mode = WAL");
}
await R.exec("PRAGMA cache_size = -12000");
await R.exec("PRAGMA auto_vacuum = INCREMENTAL");
await asyncRun("PRAGMA foreign_keys = ON");
await asyncRun("PRAGMA cache_size = -12000");
await asyncRun("PRAGMA auto_vacuum = INCREMENTAL");
// This ensures that an operating system crash or power failure will not corrupt the database.
// FULL synchronous is very safe, but it is also slower.
// Read more: https://sqlite.org/pragma.html#pragma_synchronous
await R.exec("PRAGMA synchronous = NORMAL");
if (!noLog) {
log.debug("db", "SQLite config:");
log.debug("db", await R.getAll("PRAGMA journal_mode"));
log.debug("db", await R.getAll("PRAGMA cache_size"));
log.debug("db", "SQLite Version: " + (await R.getCell("SELECT sqlite_version()")));
}
await asyncRun("PRAGMA synchronous = NORMAL");
}
/**
@@ -587,12 +597,12 @@ class Database {
let title = await setting("title");
if (title) {
console.log("Migrating Status Page");
log.info("database", "Migrating Status Page");
let statusPageCheck = await R.findOne("status_page", " slug = 'default' ");
if (statusPageCheck !== null) {
console.log("Migrating Status Page - Skip, default slug record is already existing");
log.info("database", "Migrating Status Page - Skip, default slug record is already existing");
return;
}
@@ -634,7 +644,7 @@ class Database {
await setSetting("entryPage", "statusPage-default", "general");
}
console.log("Migrating Status Page - Done");
log.info("database", "Migrating Status Page - Done");
}
}

View File

@@ -220,17 +220,11 @@ class DomainExpiry extends BeanModel {
const tld = parseTld(target);
// Avoid logging for incomplete/invalid input while editing monitors.
if (tld.isIp) {
throw new TranslatableError("domain_expiry_unsupported_is_ip", { hostname: tld.hostname });
}
// No one-letter public suffix exists; treat this as an incomplete/invalid input while typing.
if (tld.publicSuffix.length < 2) {
throw new TranslatableError("domain_expiry_public_suffix_too_short", { publicSuffix: tld.publicSuffix });
}
// It must be checked first, filter out non-ICANN domains.
if (!tld.isIcann) {
throw new TranslatableError("domain_expiry_unsupported_is_icann", {
domain: tld.domain,
// If domain is null, use hostname as fallback for better error message.
domain: tld.domain ?? tld.hostname ?? "EMPTY DOMAIN",
publicSuffix: tld.publicSuffix,
});
}

View File

@@ -98,11 +98,7 @@ class Monitor extends BeanModel {
obj.tags = await this.getTags();
}
if (
certExpiry &&
(this.type === "http" || this.type === "keyword" || this.type === "json-query") &&
this.getURLProtocol() === "https:"
) {
if (certExpiry) {
const { certExpiryDaysRemaining, validCert } = await this.getCertExpiry(this.id);
obj.certExpiryDaysRemaining = certExpiryDaysRemaining;
obj.validCert = validCert;
@@ -2063,7 +2059,7 @@ class Monitor extends BeanModel {
}
const parentActive = await Monitor.isParentActive(parent.id);
return parent.active && parentActive;
return parent.active === 1 && parentActive;
}
/**

View File

@@ -90,6 +90,11 @@ class GlobalpingMonitorType extends MonitorType {
log.debug("monitor", `Globalping create measurement: ${JSON.stringify(opts)}`);
let res = await client.createMeasurement(opts);
// Retry if the server returns a 500 error
if (!res.ok && Globalping.isHttpStatus(500, res)) {
res = await client.createMeasurement(opts);
}
if (!res.ok) {
if (Globalping.isHttpStatus(429, res)) {
throw new Error(`Failed to create measurement: ${this.formatTooManyRequestsError(hasAPIToken)}`);
@@ -187,6 +192,11 @@ class GlobalpingMonitorType extends MonitorType {
log.debug("monitor", `Globalping create measurement: ${JSON.stringify(opts)}`);
let res = await client.createMeasurement(opts);
// Retry if the server returns a 500 error
if (!res.ok && Globalping.isHttpStatus(500, res)) {
res = await client.createMeasurement(opts);
}
if (!res.ok) {
if (Globalping.isHttpStatus(429, res)) {
throw new Error(`Failed to create measurement: ${this.formatTooManyRequestsError(hasAPIToken)}`);
@@ -275,7 +285,14 @@ class GlobalpingMonitorType extends MonitorType {
log.debug("monitor", `Globalping create measurement: ${JSON.stringify(opts)}`);
let res = await client.createMeasurement(opts);
log.debug("monitor", `Globalping ${JSON.stringify(res)}`);
// Retry if the server returns a 500 error
if (!res.ok && Globalping.isHttpStatus(500, res)) {
res = await client.createMeasurement(opts);
}
if (!res.ok) {
if (Globalping.isHttpStatus(429, res)) {
throw new Error(`Failed to create measurement: ${this.formatTooManyRequestsError(hasAPIToken)}`);

View File

@@ -63,8 +63,6 @@ class GroupMonitorType extends MonitorType {
return;
}
heartbeat.status = DOWN;
let message = `Child monitors down: ${downChildren.join(", ")}`;
if (pendingChildren.length > 0) {

View File

@@ -0,0 +1,161 @@
const NotificationProvider = require("./notification-provider");
const axios = require("axios");
class Whatsapp360messenger extends NotificationProvider {
name = "Whatsapp360messenger";
/**
* @inheritdoc
*/
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
const okMsg = "Sent Successfully.";
try {
let config = {
headers: {
Accept: "application/json",
"Content-Type": "application/json",
Authorization: "Bearer " + notification.Whatsapp360messengerAuthToken,
},
};
config = this.getAxiosConfigWithProxy(config);
// Use custom template if enabled
let message = msg;
if (notification.Whatsapp360messengerUseTemplate && notification.Whatsapp360messengerTemplate) {
message = this.applyTemplate(
notification.Whatsapp360messengerTemplate,
msg,
monitorJSON,
heartbeatJSON
);
}
// Normalize recipients: support comma/semicolon-separated list
const recipients = (notification.Whatsapp360messengerRecipient || "")
.split(/[;,]/)
.map((r) => r.trim())
.filter((r) => r !== "");
// Normalize group IDs: support array (multi-select) and fallback to single value / delimited string
const rawGroupIds =
notification.Whatsapp360messengerGroupIds || notification.Whatsapp360messengerGroupId || "";
let groupIds = [];
if (Array.isArray(rawGroupIds)) {
groupIds = rawGroupIds
.map((g) => {
if (typeof g === "string") {
return g.trim();
}
if (g && typeof g === "object" && g.id) {
return String(g.id).trim();
}
return "";
})
.filter((g) => g !== "");
} else if (typeof rawGroupIds === "string" && rawGroupIds.trim() !== "") {
groupIds = rawGroupIds
.split(/[;,]/)
.map((g) => g.trim())
.filter((g) => g !== "");
}
const hasGroupId = groupIds.length > 0;
const hasRecipient = recipients.length > 0;
// Send to both if both are provided
if (hasGroupId && hasRecipient) {
// Send to all individual recipients
await Promise.all(
recipients.map((recipient) => {
const recipientData = {
phonenumber: recipient,
text: message,
};
return axios.post("https://api.360messenger.com/v2/sendMessage", recipientData, config);
})
);
// Send to all selected groups
await Promise.all(
groupIds.map((groupId) => {
const groupData = {
groupId,
text: message,
};
return axios.post("https://api.360messenger.com/v2/sendGroup", groupData, config);
})
);
return `${okMsg} (Sent to ${recipients.length} recipient(s) and ${groupIds.length} group(s))`;
} else if (hasGroupId) {
// Send to group(s) only
await Promise.all(
groupIds.map((groupId) => {
const data = {
groupId,
text: message,
};
return axios.post("https://api.360messenger.com/v2/sendGroup", data, config);
})
);
return `${okMsg} (Sent to ${groupIds.length} group(s))`;
} else if (hasRecipient) {
// Send to recipient(s) only
await Promise.all(
recipients.map((recipient) => {
const data = {
phonenumber: recipient,
text: message,
};
return axios.post("https://api.360messenger.com/v2/sendMessage", data, config);
})
);
return `${okMsg} (Sent to ${recipients.length} recipient(s))`;
} else {
throw new Error("No recipient or group specified");
}
} catch (error) {
this.throwGeneralAxiosError(error);
}
}
/**
* Apply template with variables
* @param {string} template - Template string
* @param {string} msg - Default message
* @param {object} monitorJSON - Monitor data
* @param {object} heartbeatJSON - Heartbeat data
* @returns {string} Formatted message
*/
applyTemplate(template, msg, monitorJSON, heartbeatJSON) {
try {
// Simple template replacement
let result = template;
// Replace monitor variables
if (monitorJSON) {
result = result.replace(/{{ monitorJSON\['name'\] }}/g, monitorJSON.name || "");
result = result.replace(/{{ monitorJSON\['url'\] }}/g, monitorJSON.url || "");
}
// Replace message variable
result = result.replace(/{{ msg }}/g, msg);
// Handle conditional blocks (simple if statements)
result = result.replace(/{% if monitorJSON %}([\s\S]*?){% endif %}/g, (match, content) => {
return monitorJSON ? content : "";
});
return result;
} catch (error) {
// If template parsing fails, return original message
return msg;
}
}
}
module.exports = Whatsapp360messenger;

View File

@@ -1,7 +1,7 @@
const NotificationProvider = require("./notification-provider");
const axios = require("axios");
const { setting } = require("../util-server");
const { getMonitorRelativeURL, UP, DOWN } = require("../../src/util");
const { Settings } = require("../settings");
class AlertNow extends NotificationProvider {
name = "AlertNow";
@@ -29,7 +29,7 @@ class AlertNow extends NotificationProvider {
textMsg += ` - ${msg}`;
const baseURL = await setting("primaryBaseURL");
const baseURL = await Settings.get("primaryBaseURL");
if (baseURL && monitorJSON) {
textMsg += ` >> ${baseURL + getMonitorRelativeURL(monitorJSON.id)}`;
}

View File

@@ -1,7 +1,7 @@
const NotificationProvider = require("./notification-provider");
const axios = require("axios");
const { UP, DOWN, getMonitorRelativeURL } = require("../../src/util");
const { setting } = require("../util-server");
const { Settings } = require("../settings");
const successMessage = "Sent Successfully.";
class FlashDuty extends NotificationProvider {
@@ -93,7 +93,7 @@ class FlashDuty extends NotificationProvider {
},
};
const baseURL = await setting("primaryBaseURL");
const baseURL = await Settings.get("primaryBaseURL");
if (baseURL && monitorInfo) {
options.client = "Uptime Kuma";
options.client_url = baseURL + getMonitorRelativeURL(monitorInfo.id);

View File

@@ -0,0 +1,249 @@
const NotificationProvider = require("./notification-provider");
const axios = require("axios");
const { DOWN, UP } = require("../../src/util");
class Fluxer extends NotificationProvider {
name = "fluxer";
/**
* @inheritdoc
*/
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
const okMsg = "Sent Successfully.";
try {
let config = this.getAxiosConfigWithProxy({});
const fluxerDisplayName = notification.fluxerUsername || "Uptime Kuma";
const webhookUrl = new URL(notification.fluxerWebhookUrl);
// Check if the webhook has an avatar
let webhookHasAvatar = true;
try {
const webhookInfo = await axios.get(webhookUrl.toString(), config);
webhookHasAvatar = !!webhookInfo.data.avatar;
} catch (e) {
// If we can't verify, we assume he has an avatar to avoid forcing the default avatar
webhookHasAvatar = true;
}
const messageFormat =
notification.fluxerMessageFormat || (notification.fluxerUseMessageTemplate ? "custom" : "normal");
// If heartbeatJSON is null, assume we're testing.
if (heartbeatJSON == null) {
let content = msg;
if (messageFormat === "minimalist") {
content = "Test: " + msg;
} else if (messageFormat === "custom") {
const customMessage = notification.fluxerMessageTemplate?.trim() || "";
if (customMessage !== "") {
content = await this.renderTemplate(customMessage, msg, monitorJSON, heartbeatJSON);
}
}
let fluxertestdata = {
username: fluxerDisplayName,
content: content,
};
if (!webhookHasAvatar) {
fluxertestdata.avatar_url = "https://github.com/louislam/uptime-kuma/raw/master/public/icon.png";
}
await axios.post(webhookUrl.toString(), fluxertestdata, config);
return okMsg;
}
// If heartbeatJSON is not null, we go into the normal alerting loop.
let addess = this.extractAddress(monitorJSON);
// Minimalist: status + name only (is down / is up; no "back up" — may be first trigger)
if (messageFormat === "minimalist") {
const content =
heartbeatJSON["status"] === DOWN
? "🔴 " + monitorJSON["name"] + " is down."
: "🟢 " + monitorJSON["name"] + " is up.";
let payload = {
username: fluxerDisplayName,
content: content,
};
if (!webhookHasAvatar) {
payload.avatar_url = "https://github.com/louislam/uptime-kuma/raw/master/public/icon.png";
}
await axios.post(webhookUrl.toString(), payload, config);
return okMsg;
}
// Custom template: send only content (no embeds)
const useCustomTemplate =
messageFormat === "custom" && (notification.fluxerMessageTemplate?.trim() || "") !== "";
if (useCustomTemplate) {
const content = await this.renderTemplate(
notification.fluxerMessageTemplate.trim(),
msg,
monitorJSON,
heartbeatJSON
);
let payload = {
username: fluxerDisplayName,
content: content,
};
if (!webhookHasAvatar) {
payload.avatar_url = "https://github.com/louislam/uptime-kuma/raw/master/public/icon.png";
}
await axios.post(webhookUrl.toString(), payload, config);
return okMsg;
}
if (heartbeatJSON["status"] === DOWN) {
const wentOfflineTimestamp = Math.floor(new Date(heartbeatJSON["time"]).getTime() / 1000);
let fluxerdowndata = {
username: fluxerDisplayName,
embeds: [
{
title: "❌ Your service " + monitorJSON["name"] + " went down. ❌",
color: 16711680,
fields: [
{
name: "Service Name",
value: monitorJSON["name"],
},
...(!notification.disableUrl && addess
? [
{
name: monitorJSON["type"] === "push" ? "Service Type" : "Service URL",
value: addess,
},
]
: []),
{
name: "Went Offline",
// F for full date/time
value: `<t:${wentOfflineTimestamp}:F>`,
},
{
name: `Time (${heartbeatJSON["timezone"]})`,
value: heartbeatJSON["localDateTime"],
},
{
name: "Error",
value: heartbeatJSON["msg"] == null ? "N/A" : heartbeatJSON["msg"],
},
],
},
],
};
if (!webhookHasAvatar) {
fluxerdowndata.avatar_url = "https://github.com/louislam/uptime-kuma/raw/master/public/icon.png";
}
if (notification.fluxerPrefixMessage) {
fluxerdowndata.content = notification.fluxerPrefixMessage;
}
await axios.post(webhookUrl.toString(), fluxerdowndata, config);
return okMsg;
} else if (heartbeatJSON["status"] === UP) {
const backOnlineTimestamp = Math.floor(new Date(heartbeatJSON["time"]).getTime() / 1000);
let downtimeDuration = null;
let wentOfflineTimestamp = null;
if (heartbeatJSON["lastDownTime"]) {
wentOfflineTimestamp = Math.floor(new Date(heartbeatJSON["lastDownTime"]).getTime() / 1000);
downtimeDuration = this.formatDuration(backOnlineTimestamp - wentOfflineTimestamp);
}
let fluxerupdata = {
username: fluxerDisplayName,
embeds: [
{
title: "✅ Your service " + monitorJSON["name"] + " is up! ✅",
color: 65280,
fields: [
{
name: "Service Name",
value: monitorJSON["name"],
},
...(!notification.disableUrl && addess
? [
{
name: monitorJSON["type"] === "push" ? "Service Type" : "Service URL",
value: addess,
},
]
: []),
...(wentOfflineTimestamp
? [
{
name: "Went Offline",
// F for full date/time
value: `<t:${wentOfflineTimestamp}:F>`,
},
]
: []),
...(downtimeDuration
? [
{
name: "Downtime Duration",
value: downtimeDuration,
},
]
: []),
// Show server timezone for parity with the DOWN notification embed
{
name: `Time (${heartbeatJSON["timezone"]})`,
value: heartbeatJSON["localDateTime"],
},
...(heartbeatJSON["ping"] != null
? [
{
name: "Ping",
value: heartbeatJSON["ping"] + " ms",
},
]
: []),
],
},
],
};
if (!webhookHasAvatar) {
fluxerupdata.avatar_url = "https://github.com/louislam/uptime-kuma/raw/master/public/icon.png";
}
if (notification.fluxerPrefixMessage) {
fluxerupdata.content = notification.fluxerPrefixMessage;
}
await axios.post(webhookUrl.toString(), fluxerupdata, config);
return okMsg;
}
} catch (error) {
this.throwGeneralAxiosError(error);
}
}
/**
* Format duration as human-readable string (e.g., "1h 23m", "45m 30s")
* TODO: Update below to `Intl.DurationFormat("en", { style: "short" }).format(duration)` once we are on a newer node version
* @param {number} timeInSeconds The time in seconds to format a duration for
* @returns {string} The formatted duration
*/
formatDuration(timeInSeconds) {
const hours = Math.floor(timeInSeconds / 3600);
const minutes = Math.floor((timeInSeconds % 3600) / 60);
const seconds = timeInSeconds % 60;
const durationParts = [];
if (hours > 0) {
durationParts.push(`${hours}h`);
}
if (minutes > 0) {
durationParts.push(`${minutes}m`);
}
if (seconds > 0 && hours === 0) {
// Only show seconds if less than an hour
durationParts.push(`${seconds}s`);
}
return durationParts.length > 0 ? durationParts.join(" ") : "0s";
}
}
module.exports = Fluxer;

View File

@@ -1,7 +1,7 @@
const NotificationProvider = require("./notification-provider");
const axios = require("axios");
const { setting } = require("../util-server");
const { getMonitorRelativeURL, UP } = require("../../src/util");
const { Settings } = require("../settings");
class GoogleChat extends NotificationProvider {
name = "GoogleChat";
@@ -91,7 +91,7 @@ class GoogleChat extends NotificationProvider {
}
// add button for monitor link if available
const baseURL = await setting("primaryBaseURL");
const baseURL = await Settings.get("primaryBaseURL");
if (baseURL) {
const urlPath = monitorJSON ? getMonitorRelativeURL(monitorJSON.id) : "/";
sectionWidgets.push({

View File

@@ -2,6 +2,7 @@ const { Liquid } = require("liquidjs");
const { DOWN } = require("../../src/util");
const { HttpProxyAgent } = require("http-proxy-agent");
const { HttpsProxyAgent } = require("https-proxy-agent");
const { SocksProxyAgent } = require("socks-proxy-agent");
class NotificationProvider {
/**
@@ -183,6 +184,10 @@ class NotificationProvider {
const agent = new HttpsProxyAgent(proxyEnv);
axiosConfig.httpAgent = agent;
axiosConfig.httpsAgent = agent;
} else if (["socks:", "socks4:", "socks5:", "socks5h:"].includes(proxyUrl.protocol)) {
const agent = new SocksProxyAgent(proxyEnv);
axiosConfig.httpAgent = agent;
axiosConfig.httpsAgent = agent;
}
axiosConfig.proxy = false;

View File

@@ -1,7 +1,7 @@
const NotificationProvider = require("./notification-provider");
const axios = require("axios");
const { UP, DOWN, getMonitorRelativeURL } = require("../../src/util");
const { setting } = require("../util-server");
const { Settings } = require("../settings");
let successMessage = "Sent Successfully.";
class PagerDuty extends NotificationProvider {
@@ -95,7 +95,7 @@ class PagerDuty extends NotificationProvider {
},
};
const baseURL = await setting("primaryBaseURL");
const baseURL = await Settings.get("primaryBaseURL");
if (baseURL && monitorInfo) {
options.client = "Uptime Kuma";
options.client_url = baseURL + getMonitorRelativeURL(monitorInfo.id);

View File

@@ -1,7 +1,7 @@
const NotificationProvider = require("./notification-provider");
const axios = require("axios");
const { UP, DOWN, getMonitorRelativeURL } = require("../../src/util");
const { setting } = require("../util-server");
const { Settings } = require("../settings");
let successMessage = "Sent Successfully.";
class PagerTree extends NotificationProvider {
@@ -79,7 +79,7 @@ class PagerTree extends NotificationProvider {
},
};
const baseURL = await setting("primaryBaseURL");
const baseURL = await Settings.get("primaryBaseURL");
if (baseURL && monitorJSON) {
options.client = "Uptime Kuma";
options.client_url = baseURL + getMonitorRelativeURL(monitorJSON.id);

View File

@@ -1,8 +1,8 @@
const NotificationProvider = require("./notification-provider");
const axios = require("axios");
const Slack = require("./slack");
const { setting } = require("../util-server");
const { getMonitorRelativeURL, DOWN } = require("../../src/util");
const { Settings } = require("../settings");
class RocketChat extends NotificationProvider {
name = "rocket.chat";
@@ -50,7 +50,7 @@ class RocketChat extends NotificationProvider {
await Slack.deprecateURL(notification.rocketbutton);
}
const baseURL = await setting("primaryBaseURL");
const baseURL = await Settings.get("primaryBaseURL");
if (baseURL) {
data.attachments[0].title_link = baseURL + getMonitorRelativeURL(monitorJSON.id);

View File

@@ -11,8 +11,14 @@ class Signal extends NotificationProvider {
const okMsg = "Sent Successfully.";
try {
let message = msg;
if (notification.signalUseTemplate) {
message = await this.renderTemplate(notification.signalTemplate, msg, monitorJSON, heartbeatJSON);
}
let data = {
message: msg,
message,
number: notification.signalNumber,
recipients: notification.signalRecipients.replace(/\s/g, "").split(","),
};

View File

@@ -1,7 +1,7 @@
const NotificationProvider = require("./notification-provider");
const axios = require("axios");
const { UP, DOWN, getMonitorRelativeURL } = require("../../src/util");
const { setting } = require("../util-server");
const { Settings } = require("../settings");
let successMessage = "Sent Successfully.";
class Splunk extends NotificationProvider {
@@ -94,7 +94,7 @@ class Splunk extends NotificationProvider {
},
};
const baseURL = await setting("primaryBaseURL");
const baseURL = await Settings.get("primaryBaseURL");
if (baseURL && monitorInfo) {
options.client = "Uptime Kuma";
options.client_url = baseURL + getMonitorRelativeURL(monitorInfo.id);

View File

@@ -1,7 +1,7 @@
const NotificationProvider = require("./notification-provider");
const axios = require("axios");
const { setting } = require("../util-server");
const { getMonitorRelativeURL } = require("../../src/util");
const { Settings } = require("../settings");
class Stackfield extends NotificationProvider {
name = "stackfield";
@@ -23,7 +23,7 @@ class Stackfield extends NotificationProvider {
textMsg += `\n${msg}`;
const baseURL = await setting("primaryBaseURL");
const baseURL = await Settings.get("primaryBaseURL");
if (baseURL) {
textMsg += `\n${baseURL + getMonitorRelativeURL(monitorJSON.id)}`;
}

View File

@@ -12,6 +12,7 @@ const CallMeBot = require("./notification-providers/call-me-bot");
const SMSC = require("./notification-providers/smsc");
const DingDing = require("./notification-providers/dingding");
const Discord = require("./notification-providers/discord");
const Fluxer = require("./notification-providers/fluxer");
const Elks = require("./notification-providers/46elks");
const Feishu = require("./notification-providers/feishu");
const Notifery = require("./notification-providers/notifery");
@@ -86,6 +87,7 @@ const SMSPlanet = require("./notification-providers/sms-planet");
const SpugPush = require("./notification-providers/spugpush");
const SMSIR = require("./notification-providers/smsir");
const { commandExists } = require("./util-server");
const Whatsapp360messenger = require("./notification-providers/360messenger");
const Webpush = require("./notification-providers/Webpush");
const HaloPSA = require("./notification-providers/HaloPSA");
@@ -116,6 +118,7 @@ class Notification {
new SMSC(),
new DingDing(),
new Discord(),
new Fluxer(),
new Elks(),
new Feishu(),
new FreeMobile(),
@@ -189,6 +192,7 @@ class Notification {
new Notifery(),
new SMSIR(),
new SendGrid(),
new Whatsapp360messenger(),
new Webpush(),
new HaloPSA(),
];

View File

@@ -245,8 +245,10 @@ class Prometheus {
try {
monitorCertDaysRemaining.remove(this.monitorLabelValues);
monitorCertIsValid.remove(this.monitorLabelValues);
monitorUptimeRatio.remove(this.monitorLabelValues);
monitorAverageResponseTimeSeconds.remove(this.monitorLabelValues);
["1d", "30d", "365d"].forEach((window) => {
monitorUptimeRatio.remove({ ...this.monitorLabelValues, window });
monitorAverageResponseTimeSeconds.remove({ ...this.monitorLabelValues, window });
});
monitorResponseTime.remove(this.monitorLabelValues);
monitorStatus.remove(this.monitorLabelValues);
} catch (e) {

View File

@@ -1,6 +1,5 @@
let express = require("express");
const {
setting,
allowDevAllOrigin,
allowAllOrigin,
percentageToColor,
@@ -18,6 +17,7 @@ const { makeBadge } = require("badge-maker");
const { Prometheus } = require("../prometheus");
const Database = require("../database");
const { UptimeCalculator } = require("../uptime-calculator");
const { Settings } = require("../settings");
let router = express.Router();
@@ -30,7 +30,7 @@ router.get("/api/entry-page", async (request, response) => {
let result = {};
let hostname = request.hostname;
if ((await setting("trustProxy")) && request.headers["x-forwarded-host"]) {
if ((await Settings.get("trustProxy")) && request.headers["x-forwarded-host"]) {
hostname = request.headers["x-forwarded-host"];
}
@@ -129,7 +129,7 @@ router.all("/api/push/:pushToken", async (request, response) => {
Monitor.sendStats(io, monitor.id, monitor.user_id);
try {
new Prometheus(monitor, []).update(bean, undefined);
new Prometheus(monitor, await monitor.getTags()).update(bean, undefined);
} catch (e) {
log.error("prometheus", "Please submit an issue to our GitHub repo. Prometheus update error: ", e.message);
}
@@ -168,17 +168,7 @@ router.get("/api/badge/:id/status", cache("5 minutes"), async (request, response
throw new Error("Invalid monitor ID");
}
const overrideValue = value !== undefined ? parseInt(value) : undefined;
let publicMonitor = await R.getRow(
`
SELECT monitor_group.monitor_id FROM monitor_group, \`group\`
WHERE monitor_group.group_id = \`group\`.id
AND monitor_group.monitor_id = ?
AND public = 1
`,
[requestedMonitorId]
);
const publicMonitor = await isMonitorPublic(requestedMonitorId);
const badgeValues = { style };
if (!publicMonitor) {
@@ -256,16 +246,7 @@ router.get("/api/badge/:id/uptime/:duration?", cache("5 minutes"), async (reques
requestedDuration = `${requestedDuration}h`;
}
let publicMonitor = await R.getRow(
`
SELECT monitor_group.monitor_id FROM monitor_group, \`group\`
WHERE monitor_group.group_id = \`group\`.id
AND monitor_group.monitor_id = ?
AND public = 1
`,
[requestedMonitorId]
);
const publicMonitor = await isMonitorPublic(requestedMonitorId);
const badgeValues = { style };
if (!publicMonitor) {
@@ -331,19 +312,20 @@ router.get("/api/badge/:id/ping/:duration?", cache("5 minutes"), async (request,
}
// Check if monitor is public
const publicMonitor = await isMonitorPublic(requestedMonitorId);
const uptimeCalculator = await UptimeCalculator.getUptimeCalculator(requestedMonitorId);
const publicAvgPing = uptimeCalculator.getDataByDuration(requestedDuration).avgPing;
const avgPing = uptimeCalculator.getDataByDuration(requestedDuration).avgPing;
const badgeValues = { style };
if (!publicAvgPing) {
if (!publicMonitor) {
// return a "N/A" badge in naColor (grey), if monitor is not public / not available / non exsitant
badgeValues.message = "N/A";
badgeValues.color = badgeConstants.naColor;
} else {
const avgPing = parseInt(overrideValue ?? publicAvgPing);
const avgPingValue = parseInt(overrideValue ?? avgPing);
badgeValues.color = color;
// use a given, custom labelColor or use the default badge label color (defined by badge-maker)
@@ -353,7 +335,7 @@ router.get("/api/badge/:id/ping/:duration?", cache("5 minutes"), async (request,
labelPrefix,
label ?? `Avg. Ping (${requestedDuration.slice(0, -1)}${labelSuffix})`,
]);
badgeValues.message = filterAndJoin([prefix, avgPing, suffix]);
badgeValues.message = filterAndJoin([prefix, avgPingValue, suffix]);
}
// build the SVG based on given values
@@ -467,17 +449,7 @@ router.get("/api/badge/:id/cert-exp", cache("5 minutes"), async (request, respon
}
const overrideValue = value && parseFloat(value);
let publicMonitor = await R.getRow(
`
SELECT monitor_group.monitor_id FROM monitor_group, \`group\`
WHERE monitor_group.group_id = \`group\`.id
AND monitor_group.monitor_id = ?
AND public = 1
`,
[requestedMonitorId]
);
const publicMonitor = await isMonitorPublic(requestedMonitorId);
const badgeValues = { style };
if (!publicMonitor) {
@@ -554,17 +526,7 @@ router.get("/api/badge/:id/response", cache("5 minutes"), async (request, respon
}
const overrideValue = value && parseFloat(value);
let publicMonitor = await R.getRow(
`
SELECT monitor_group.monitor_id FROM monitor_group, \`group\`
WHERE monitor_group.group_id = \`group\`.id
AND monitor_group.monitor_id = ?
AND public = 1
`,
[requestedMonitorId]
);
const publicMonitor = await isMonitorPublic(requestedMonitorId);
const badgeValues = { style };
if (!publicMonitor) {
@@ -656,4 +618,22 @@ function determineStatus(status, previousHeartbeat, maxretries, isUpsideDown, be
}
}
/**
* Check whether a monitor is publc
* @param {number} monitorID - Monitor id
* @returns {Promise<boolean>} true if the monitor is public, otherwise false
*/
async function isMonitorPublic(monitorID) {
let publicMonitor = await R.getRow(
`
SELECT monitor_group.monitor_id FROM monitor_group, \`group\`
WHERE monitor_group.group_id = \`group\`.id
AND monitor_group.monitor_id = ?
AND public = 1
`,
[monitorID]
);
return !!publicMonitor;
}
module.exports = router;

View File

@@ -49,6 +49,8 @@ const args = require("args-parser")(process.argv);
const { sleep, log, getRandomInt, genSecret, isDev } = require("../src/util");
const config = require("./config");
process.title = "uptime-kuma";
log.debug("server", "Arguments");
log.debug("server", args);
@@ -110,6 +112,7 @@ const {
shake256,
SHAKE256_LENGTH,
allowDevAllOrigin,
printServerUrls,
} = require("./util-server");
log.debug("server", "Importing Notification");
@@ -1002,7 +1005,8 @@ let needSetup = false;
}
});
socket.on("checkMointor", async (partial, callback) => {
// partial { type, url, hostname, grpcUrl }
socket.on("checkDomain", async (partial, callback) => {
try {
checkLogin(socket);
const DomainExpiry = require("./model/domain_expiry");
@@ -1583,7 +1587,7 @@ let needSetup = false;
msg,
});
} catch (e) {
console.error(e);
log.error("server", e);
callback({
ok: false,
@@ -1741,11 +1745,7 @@ let needSetup = false;
await server.start();
server.httpServer.listen(port, hostname, async () => {
if (hostname) {
log.info("server", `Listening on ${hostname}:${port}`);
} else {
log.info("server", `Listening on ${port}`);
}
printServerUrls("server", port, hostname);
await startMonitors();
// Put this here. Start background jobs after the db and server is ready to prevent clear up during db migration.

View File

@@ -4,7 +4,7 @@ const expressStaticGzip = require("express-static-gzip");
const fs = require("fs");
const path = require("path");
const Database = require("./database");
const { allowDevAllOrigin } = require("./util-server");
const { allowDevAllOrigin, printServerUrls } = require("./util-server");
const mysql = require("mysql2/promise");
/**
@@ -307,9 +307,8 @@ class SetupDatabase {
});
tempServer = app.listen(port, hostname, () => {
log.info("setup-database", `Starting Setup Database on ${port}`);
let domain = hostname ? hostname : "localhost";
log.info("setup-database", `Open http://${domain}:${port} in your browser`);
log.info("setup-database", "Starting Setup Database");
printServerUrls("setup-database", port, hostname);
log.info("setup-database", "Waiting for user action...");
});
});

View File

@@ -59,7 +59,7 @@ module.exports.apiKeySocketHandler = (socket) => {
ok: true,
});
} catch (e) {
console.error(e);
log.error("apikeys", e);
callback({
ok: false,
msg: e.message,

View File

@@ -106,11 +106,11 @@ module.exports.autoStart = async (token) => {
} else {
// Override the current token via args or env var
await setSetting("cloudflaredTunnelToken", token);
console.log("Use cloudflared token from args or env var");
log.info("cloudflare", "Use cloudflared token from args or env var");
}
if (token) {
console.log("Start cloudflared");
log.info("cloudflare", "Start cloudflared");
cloudflared.token = token;
cloudflared.start();
}

View File

@@ -65,7 +65,7 @@ module.exports.maintenanceSocketHandler = (socket) => {
maintenanceID: bean.id,
});
} catch (e) {
console.error(e);
log.error("maintenance", e);
callback({
ok: false,
msg: e.message,
@@ -165,7 +165,7 @@ module.exports.maintenanceSocketHandler = (socket) => {
ok: true,
});
} catch (e) {
console.error(e);
log.error("maintenance", e);
callback({
ok: false,
msg: e.message,
@@ -189,7 +189,7 @@ module.exports.maintenanceSocketHandler = (socket) => {
monitors,
});
} catch (e) {
console.error(e);
log.error("maintenance", e);
callback({
ok: false,
msg: e.message,
@@ -213,7 +213,7 @@ module.exports.maintenanceSocketHandler = (socket) => {
statusPages,
});
} catch (e) {
console.error(e);
log.error("maintenance", e);
callback({
ok: false,
msg: e.message,

View File

@@ -1,5 +1,5 @@
const { R } = require("redbean-node");
const { checkLogin, setSetting } = require("../util-server");
const { checkLogin } = require("../util-server");
const dayjs = require("dayjs");
const { log } = require("../../src/util");
const ImageDataURI = require("../image-data-uri");
@@ -7,6 +7,7 @@ const Database = require("../database");
const apicache = require("../modules/apicache");
const StatusPage = require("../model/status_page");
const { UptimeKumaServer } = require("../uptime-kuma-server");
const { Settings } = require("../settings");
/**
* Validates incident data
@@ -338,6 +339,10 @@ module.exports.statusPageSocketHandler = (socket) => {
statusPage.modified_date = R.isoDateTime();
statusPage.analytics_id = config.analyticsId;
statusPage.analytics_script_url = config.analyticsScriptUrl;
const validAnalyticsTypes = ["google", "umami", "plausible", "matomo"];
if (config.analyticsType !== null && !validAnalyticsTypes.includes(config.analyticsType)) {
throw new Error("Invalid analytics type");
}
statusPage.analytics_type = config.analyticsType;
await R.store(statusPage);
@@ -408,7 +413,7 @@ module.exports.statusPageSocketHandler = (socket) => {
// Also change entry page to new slug if it is the default one, and slug is changed.
if (server.entryPage === "statusPage-" + slug && statusPage.slug !== slug) {
server.entryPage = "statusPage-" + statusPage.slug;
await setSetting("entryPage", server.entryPage, "general");
await Settings.set("entryPage", server.entryPage, "general");
}
apicache.clear();
@@ -465,7 +470,7 @@ module.exports.statusPageSocketHandler = (socket) => {
slug: slug,
});
} catch (error) {
console.error(error);
log.error("socket", error);
callback({
ok: false,
msg: error.message,
@@ -486,7 +491,7 @@ module.exports.statusPageSocketHandler = (socket) => {
// Reset entry page if it is the default one.
if (server.entryPage === "statusPage-" + slug) {
server.entryPage = "dashboard";
await setSetting("entryPage", server.entryPage, "general");
await Settings.set("entryPage", server.entryPage, "general");
}
// No need to delete records from `status_page_cname`, because it has cascade foreign key.

View File

@@ -985,3 +985,34 @@ async function commandExists(command) {
}
}
module.exports.commandExists = commandExists;
/**
* Log the server's listening URLs, similar to Vite's dev server output.
* When no hostname is specified (bound to all interfaces), it prints
* localhost plus every non-internal network address.
* @param {string} tag Log tag (e.g. "server", "setup-database")
* @param {number} port Port number
* @param {string} hostname Bound hostname, if any
* @returns {void}
*/
module.exports.printServerUrls = (tag, port, hostname) => {
if (hostname) {
log.info(tag, `Listening on http://${hostname}:${port}`);
return;
}
const { networkInterfaces } = require("os");
const nets = networkInterfaces();
log.info(tag, "Listening on:");
log.info(tag, ` Local: http://localhost:${port}`);
for (const iface of Object.values(nets)) {
for (const addr of iface) {
if (!addr.internal) {
const host = addr.family === "IPv6" ? `[${addr.address}]` : addr.address;
log.info(tag, ` Network: http://${host}:${port}`);
}
}
}
};

View File

@@ -1,6 +1,6 @@
const express = require("express");
const http = require("node:http");
const { log } = require("../../src/util");
const { printServerUrls } = require("../util-server");
/**
* SimpleMigrationServer
@@ -64,11 +64,7 @@ class SimpleMigrationServer {
return new Promise((resolve) => {
this.server.listen(port, hostname, () => {
if (hostname) {
log.info("migration", `Migration server is running on http://${hostname}:${port}`);
} else {
log.info("migration", `Migration server is running on http://localhost:${port}`);
}
printServerUrls("migration", port, hostname);
resolve();
});
});

View File

@@ -739,11 +739,6 @@ export default {
}
}
.actions-row {
display: flex;
align-items: center;
}
.selection-controls {
margin-top: 5px;
display: flex;
@@ -822,10 +817,6 @@ export default {
transition: none !important;
}
.monitor-item {
width: 100%;
}
.tags {
margin-top: 4px;
padding-left: 67px;
@@ -834,11 +825,6 @@ export default {
gap: 0;
}
.bottom-style {
padding-left: 67px;
margin-top: 5px;
}
@media (max-width: 549px), (min-width: 770px) and (max-width: 1149px), (min-width: 1200px) and (max-width: 1499px) {
.selection-controls {
.selected-count {

View File

@@ -105,7 +105,7 @@
</li>
</template>
</MonitorListFilterDropdown>
<MonitorListFilterDropdown :filterActive="filterState.tags?.length > 0">
<MonitorListFilterDropdown :filterActive="filterState.tags?.length > 0" @open-menu="getExistingTags">
<template #status>
<Tag
v-if="filterState.tags?.length === 1"

View File

@@ -1,5 +1,5 @@
<template>
<div tabindex="-1" class="dropdown" @focusin="open = true" @focusout="handleFocusOut">
<div tabindex="-1" class="dropdown" @focusin="openMenu" @focusout="handleFocusOut">
<button type="button" class="filter-dropdown-status" :class="{ active: filterActive }" tabindex="0">
<div class="px-1 d-flex align-items-center">
<slot name="status"></slot>
@@ -23,12 +23,18 @@ export default {
required: true,
},
},
emits: ["openMenu"],
data() {
return {
open: false,
};
},
methods: {
openMenu() {
this.$emit("openMenu");
this.open = true;
},
handleFocusOut(e) {
if (e.relatedTarget != null && this.$el.contains(e.relatedTarget)) {
return;

View File

@@ -23,12 +23,7 @@
<router-link :to="monitorURL(monitor.id)" class="item" :class="{ disabled: !monitor.active }">
<div class="row">
<div
class="col-9 col-xl-6 small-padding d-flex gap-2 align-items-center"
:class="{
'monitor-item': $root.userHeartbeatBar == 'bottom' || $root.userHeartbeatBar == 'none',
}"
>
<div class="small-padding d-flex gap-2 align-items-center" :class="monitorStyle">
<div class="me-1">
<Uptime :monitor="monitor" type="24" :pill="true" />
</div>
@@ -167,6 +162,15 @@ export default {
marginLeft: `${20 * this.depth}px`,
};
},
monitorStyle() {
const isFullWidth = this.$root.userHeartbeatBar === "bottom" || this.$root.userHeartbeatBar === "none";
const c = {};
if (!isFullWidth) {
c["col-9"] = true;
c["col-xl-6"] = true;
}
return c;
},
},
watch: {
isSelectMode() {
@@ -329,10 +333,6 @@ export default {
padding-right: 5px !important;
}
// .monitor-item {
// width: 100%;
// }
.tags {
margin-top: 4px;
padding-left: 4px;
@@ -390,4 +390,9 @@ export default {
cursor: grabbing;
}
}
.bottom-style {
margin-left: -10px;
margin-top: 5px;
}
</style>

View File

@@ -215,6 +215,7 @@ export default {
bale: "Bale",
Bitrix24: "Bitrix24",
discord: "Discord",
fluxer: "Fluxer",
GoogleChat: "Google Chat (Google Workspace)",
gorush: "Gorush",
gotify: "Gotify",
@@ -244,6 +245,7 @@ export default {
whapi: "WhatsApp (Whapi)",
evolution: "WhatsApp (Evolution)",
waha: "WhatsApp (WAHA)",
Whatsapp360messenger: "WhatsApp (360messenger)",
};
// Push Services - Push notification services

View File

@@ -310,15 +310,15 @@ export default {
// Show ping values if it was up in this period
avgPingData.push({
x,
y: datapoint.up > 0 && datapoint.avgPing > 0 ? datapoint.avgPing : null,
y: datapoint.up > 0 && datapoint.avgPing != null ? datapoint.avgPing : null,
});
minPingData.push({
x,
y: datapoint.up > 0 && datapoint.avgPing > 0 ? datapoint.minPing : null,
y: datapoint.up > 0 && datapoint.avgPing != null ? datapoint.minPing : null,
});
maxPingData.push({
x,
y: datapoint.up > 0 && datapoint.avgPing > 0 ? datapoint.maxPing : null,
y: datapoint.up > 0 && datapoint.avgPing != null ? datapoint.maxPing : null,
});
downData.push({
x,

View File

@@ -0,0 +1,300 @@
<template>
<div class="mb-3">
<label for="360messenger-auth-token" class="form-label">{{ $t("360messengerAuthToken") }}</label>
<HiddenInput
id="360messenger-auth-token"
v-model="$parent.notification.Whatsapp360messengerAuthToken"
:required="true"
autocomplete="new-password"
></HiddenInput>
<i18n-t tag="div" keypath="360messengerWayToGetUrlAndToken" class="form-text">
<a href="https://360messenger.com/en/uptime-kuma" target="_blank">
https://360messenger.com/en/uptime-kuma
</a>
</i18n-t>
</div>
<div class="mb-3">
<label for="360messenger-recipient" class="form-label">{{ $t("360messengerRecipient") }}</label>
<input
id="360messenger-recipient"
v-model="$parent.notification.Whatsapp360messengerRecipient"
type="text"
class="form-control"
placeholder="447488888888, 447499999999"
:required="!hasAnySelectedGroup"
/>
<div class="form-text">{{ $t("360messengerWayToWriteRecipient", ["447488888888"]) }}</div>
</div>
<!-- Checkbox to enable/disable Combobox -->
<div class="mb-3 form-check form-switch">
<input id="360messenger-enable-options" v-model="isOptionsEnabled" type="checkbox" class="form-check-input" />
<label for="360messenger-enable-options" class="form-check-label">
{{ $t("360messengerEnableSendToGroup") }}
</label>
</div>
<!-- Group selection using existing VueMultiselect -->
<div class="mb-3">
<label for="360messenger-group-list" class="form-label">
{{ $t("360messengerGroupList") }}
</label>
<VueMultiselect
id="360messenger-group-list"
v-model="$parent.notification.Whatsapp360messengerGroupIds"
:options="groupOptions"
:multiple="true"
:close-on-select="false"
:clear-on-select="false"
:preserve-search="true"
:placeholder="$t('360messengerSelectGroupList')"
:preselect-first="false"
:max-height="400"
:taggable="false"
:disabled="!isOptionsEnabled || isLoadingGroups"
label="label"
track-by="id"
>
<template #noOptions>
<div class="multiselect__option">
<span v-if="isLoadingGroups">{{ $t("Loading...") }}</span>
<span v-else>{{ $t("360messengerErrorNoGroups") }}</span>
</div>
</template>
</VueMultiselect>
<div v-if="errorMessage" class="text-danger mt-1">{{ errorMessage }}</div>
</div>
<div class="mb-3">
<div class="form-check form-switch">
<input
v-model="$parent.notification.Whatsapp360messengerUseTemplate"
class="form-check-input"
type="checkbox"
/>
<label class="form-check-label">{{ $t("360messengerCustomMessageTemplate") }}</label>
</div>
<div class="form-text">
{{ $t("360messengerEnableCustomMessage") }}
</div>
</div>
<template v-if="$parent.notification.Whatsapp360messengerUseTemplate">
<div class="mb-3">
<label class="form-label" for="360messenger-template">{{ $t("360messengerMessageTemplate") }}</label>
<TemplatedTextarea
id="360messenger-template"
v-model="$parent.notification.Whatsapp360messengerTemplate"
:required="true"
:placeholder="Whatsapp360messengerTemplatedTextareaPlaceholder"
></TemplatedTextarea>
</div>
</template>
</template>
<script>
import HiddenInput from "../HiddenInput.vue";
import TemplatedTextarea from "../TemplatedTextarea.vue";
import VueMultiselect from "vue-multiselect";
export default {
components: {
HiddenInput,
TemplatedTextarea,
VueMultiselect,
},
data() {
return {
isOptionsEnabled: false,
groups: [],
isLoadingGroups: false,
errorMessage: "",
};
},
computed: {
Whatsapp360messengerTemplatedTextareaPlaceholder() {
return this.$t("Example:", [
`
Uptime Kuma Alert{% if monitorJSON %} - {{ monitorJSON['name'] }}{% endif %}
{{ msg }}
`,
]);
},
groupOptions() {
return this.groups.map((g) => ({
id: g.id,
label: `${g.id} - ${g.name}`,
}));
},
selectedGroupIds() {
const raw =
this.$parent.notification.Whatsapp360messengerGroupIds ||
this.$parent.notification.Whatsapp360messengerGroupId;
if (Array.isArray(raw)) {
return raw
.map((item) => {
if (typeof item === "string") {
return item.trim();
}
if (item && typeof item === "object" && item.id) {
return String(item.id).trim();
}
return "";
})
.filter((id) => id !== "");
}
if (typeof raw === "string" && raw.trim() !== "") {
return raw
.split(/[;,]/)
.map((id) => id.trim())
.filter((id) => id !== "");
}
return [];
},
hasAnySelectedGroup() {
return this.selectedGroupIds.length > 0;
},
},
watch: {
// When checkbox is enabled, fetch groups from API
isOptionsEnabled(newValue, oldValue) {
if (newValue) {
this.fetchGroups();
} else if (oldValue && !this.errorMessage) {
// Only clear if user manually unchecked (not due to error)
this.$parent.notification.Whatsapp360messengerGroupIds = [];
this.$parent.notification.Whatsapp360messengerGroupId = "";
this.groups = [];
}
},
"$parent.notification.Whatsapp360messengerGroupIds": {
immediate: true,
handler(value) {
if (Array.isArray(value)) {
return;
}
let source = value;
if (!source && this.$parent.notification.Whatsapp360messengerGroupId) {
source = this.$parent.notification.Whatsapp360messengerGroupId;
}
let normalized = [];
if (typeof source === "string" && source.trim() !== "") {
normalized = source
.split(/[;,]/)
.map((v) => v.trim())
.filter((v) => v !== "");
}
this.$parent.notification.Whatsapp360messengerGroupIds = normalized;
},
},
},
methods: {
toggleDropdown() {
if (!this.isOptionsEnabled || this.isLoadingGroups) {
return;
}
this.isDropdownOpen = !this.isDropdownOpen;
},
toggleGroupId(id) {
const trimmed = typeof id === "string" ? id.trim() : "";
if (!trimmed) {
return;
}
if (this.selectedGroupIds.includes(trimmed)) {
this.removeGroupId(trimmed);
} else {
this.addGroupId(trimmed);
}
},
addGroupId(id) {
const trimmed = typeof id === "string" ? id.trim() : "";
if (!trimmed) {
return;
}
const list = this.$parent.notification.Whatsapp360messengerGroupIds;
if (!Array.isArray(list)) {
return;
}
// Prefer the new array-based field going forward
this.$parent.notification.Whatsapp360messengerGroupId = "";
if (!list.includes(trimmed)) {
list.push(trimmed);
}
},
removeGroupId(id) {
const list = this.$parent.notification.Whatsapp360messengerGroupIds;
if (!Array.isArray(list)) {
return;
}
this.$parent.notification.Whatsapp360messengerGroupIds = list.filter((x) => x !== id);
},
async fetchGroups() {
this.isLoadingGroups = true;
this.errorMessage = "";
try {
const token = this.$parent.notification.Whatsapp360messengerAuthToken;
if (!token) {
this.errorMessage = this.$t("360messengerErrorNoApiKey");
this.isLoadingGroups = false;
this.isOptionsEnabled = false;
return;
}
const response = await fetch("https://api.360messenger.com/v2/groupChat/getGroupList", {
method: "GET",
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
},
});
const result = await response.json();
if (result.success && result.data && result.data.groups) {
this.groups = result.data.groups;
if (this.groups.length === 0) {
this.errorMessage = this.$t("360messengerErrorNoGroups");
this.isOptionsEnabled = false;
}
} else {
// Handle API error response
const statusCode = result.statusCode || response.status;
const message = result.message || "Failed to load groups";
this.errorMessage = this.$t("360messengerErrorApi", { statusCode, message });
this.isOptionsEnabled = false;
}
} catch (error) {
this.errorMessage = this.$t("360messengerErrorGeneric", { message: error.message });
this.isOptionsEnabled = false;
console.error("Error fetching groups:", error);
} finally {
this.isLoadingGroups = false;
}
},
},
};
</script>
<style lang="scss" scoped>
textarea {
min-height: 150px;
}
</style>

View File

@@ -1,11 +1,10 @@
<template>
<div class="mb-3">
<label for="discord-webhook-url" class="form-label">{{ $t("Discord Webhook URL") }}</label>
<input
<HiddenInput
id="discord-webhook-url"
v-model="$parent.notification.discordWebhookUrl"
type="text"
class="form-control"
required
autocomplete="false"
/>
@@ -144,11 +143,13 @@
</div>
</template>
<script>
import HiddenInput from "../HiddenInput.vue";
import TemplatedTextarea from "../TemplatedTextarea.vue";
export default {
components: {
TemplatedTextarea,
HiddenInput,
},
mounted() {
if (!this.$parent.notification.discordChannelType) {

View File

@@ -0,0 +1,87 @@
<template>
<div class="mb-3">
<label for="fluxer-webhook-url" class="form-label">{{ $t("Fluxer Webhook URL") }}</label>
<HiddenInput
id="fluxer-webhook-url"
v-model="$parent.notification.fluxerWebhookUrl"
type="url"
required
autocomplete="false"
/>
<div class="form-text">
{{ $t("wayToGetFluxerURL") }}
</div>
</div>
<div class="mb-3">
<label for="fluxer-username" class="form-label">{{ $t("Bot Display Name") }}</label>
<input
id="fluxer-username"
v-model="$parent.notification.fluxerUsername"
type="text"
class="form-control"
autocomplete="false"
:placeholder="$root.appName"
/>
</div>
<div class="mb-3">
<label for="fluxer-prefix-message" class="form-label">{{ $t("Prefix Custom Message") }}</label>
<input
id="fluxer-prefix-message"
v-model="$parent.notification.fluxerPrefixMessage"
type="text"
class="form-control"
autocomplete="false"
:placeholder="$t('Hello @everyone is...')"
/>
</div>
<div class="mb-3">
<label for="fluxer-message-format" class="form-label">{{ $t("fluxerMessageFormat") }}</label>
<select id="fluxer-message-format" v-model="$parent.notification.fluxerMessageFormat" class="form-select">
<option value="normal">{{ $t("fluxerMessageFormatNormal") }}</option>
<option value="minimalist">{{ $t("fluxerMessageFormatMinimalist") }}</option>
<option value="custom">{{ $t("fluxerMessageFormatCustom") }}</option>
</select>
</div>
<div v-show="$parent.notification.fluxerMessageFormat === 'custom'">
<div class="mb-3">
<label for="fluxer-message-template" class="form-label">{{ $t("fluxerMessageTemplate") }}</label>
<TemplatedTextarea
id="fluxer-message-template"
v-model="$parent.notification.fluxerMessageTemplate"
:required="false"
placeholder=""
></TemplatedTextarea>
<div class="form-text">{{ $t("fluxerUseMessageTemplateDescription") }}</div>
</div>
</div>
</template>
<script>
import HiddenInput from "../HiddenInput.vue";
import TemplatedTextarea from "../TemplatedTextarea.vue";
export default {
components: {
TemplatedTextarea,
HiddenInput,
},
mounted() {
if (!this.$parent.notification.fluxerChannelType) {
this.$parent.notification.fluxerChannelType = "channel";
}
if (this.$parent.notification.disableUrl === undefined) {
this.$parent.notification.disableUrl = false;
}
// Message format: default "normal"; migrate from old checkbox
if (typeof this.$parent.notification.fluxerMessageFormat === "undefined") {
const hadCustom =
this.$parent.notification.fluxerUseMessageTemplate === true ||
!!this.$parent.notification.fluxerMessageTemplate?.trim();
this.$parent.notification.fluxerMessageFormat = hadCustom ? "custom" : "normal";
}
},
};
</script>

View File

@@ -38,7 +38,7 @@
</div>
<div class="mb-3">
<label for="notificationService" class="form-label">{{ $t("Notification Service") }}</label>
<label for="notificationService" class="form-label">{{ $t("Notification Action") }}</label>
<input
id="notificationService"
v-model="$parent.notification.notificationService"
@@ -48,13 +48,7 @@
/>
<div class="form-text">
<p>
{{
$t(
'A list of Notification Services can be found in Home Assistant under "Developer Tools > Services" search for "notification" to find your device/phone name.'
)
}}
</p>
<p>{{ $t("homeAssistantNotificationActionHelptext") }}</p>
<p>{{ $t("Automations can optionally be triggered in Home Assistant:") }}</p>
<p>
{{ $t("Trigger type:") }}

View File

@@ -52,4 +52,48 @@
</p>
</div>
</div>
<div class="mb-3">
<div class="form-check form-switch">
<input v-model="$parent.notification.signalUseTemplate" class="form-check-input" type="checkbox" />
<label class="form-check-label">{{ $t("signalUseTemplate") }}</label>
</div>
<div class="form-text">
{{ $t("signalUseTemplateDescription") }}
</div>
</div>
<template v-if="$parent.notification.signalUseTemplate">
<div class="mb-3">
<label class="form-label" for="signal-template">{{ $t("Message Template") }}</label>
<TemplatedTextarea
id="signal-template"
v-model="$parent.notification.signalTemplate"
:required="true"
:placeholder="signalTemplatedTextareaPlaceholder"
></TemplatedTextarea>
</div>
</template>
</template>
<script>
import TemplatedTextarea from "../TemplatedTextarea.vue";
export default {
components: {
TemplatedTextarea,
},
computed: {
signalTemplatedTextareaPlaceholder() {
return this.$t("Example:", [
`
Signal Alert{% if monitorJSON %} - {{ monitorJSON['name'] }}{% endif %}
{{ msg }}
`,
]);
},
},
};
</script>

View File

@@ -11,6 +11,7 @@ import CallMeBot from "./CallMeBot.vue";
import SMSC from "./SMSC.vue";
import DingDing from "./DingDing.vue";
import Discord from "./Discord.vue";
import Fluxer from "./Fluxer.vue";
import Elks from "./46elks.vue";
import Feishu from "./Feishu.vue";
import FreeMobile from "./FreeMobile.vue";
@@ -73,6 +74,7 @@ import SpugPush from "./SpugPush.vue";
import SevenIO from "./SevenIO.vue";
import Whapi from "./Whapi.vue";
import WAHA from "./WAHA.vue";
import Whatsapp360messenger from "./360messenger.vue";
import Evolution from "./Evolution.vue";
import Cellsynt from "./Cellsynt.vue";
import WPush from "./WPush.vue";
@@ -104,6 +106,7 @@ const NotificationFormList = {
smsir: SMSIR,
DingDing: DingDing,
discord: Discord,
fluxer: Fluxer,
Elks: Elks,
Feishu: Feishu,
FreeMobile: FreeMobile,
@@ -168,6 +171,7 @@ const NotificationFormList = {
evolution: Evolution,
notifery: Notifery,
waha: WAHA,
Whatsapp360messenger: Whatsapp360messenger,
gtxmessaging: GtxMessaging,
Cellsynt: Cellsynt,
WPush: WPush,

View File

@@ -1510,7 +1510,7 @@
"bulkDeleteErrorMsg": "Неуспешно изтриване на {n} монитор | Неуспешно изтриване на {n} монитора",
"screenshotDelayWarning": "По-високите стойности поддържат браузъра отворен по-дълго време, което може да увеличи използването на памет при голям брой едновременни монитори.",
"screenshotDelayDescription": "По желание изчакайте толкова милисекунди, преди да направите екранна снимка. Максимум: {maxValueMs}ms (0.5 × interval).",
"domain_expiry_unsupported_is_icann": "Домейнът \"{domain}\" не е кандидат за наблюдение на изтичането на домейн, защото неговият публичен суфикс \".{publicSuffix}\" не е ICAN",
"domain_expiry_unsupported_is_icann": "Домейнът \"{domain}\" не е кандидат за наблюдение на изтичане на домейн, тъй като неговият публичен суфикс \".{publicSuffix}\" не се управлява от ICANN",
"Sets end time based on start time": "Задава крайния час въз основа на началния час",
"Please set start time first": "Моля, първо задайте начален час",
"legacyOctopushEndpoint": "Унаследен Octopush-DM (крайна точка адрес: {url})",
@@ -1586,5 +1586,39 @@
"RegexMatch": "Въведете регулярен израз, за да съответства на стойността на записа",
"GlobalpingMonitorDescription": "Globalping предоставя достъп до хиляди сонди, хоствани от общността, за провеждане на мрежови тестове и измервания. За всички анонимни потребители е определен лимит от 250 теста на час. За да удвоите ограничението до 500 на час, моля, запазете токена си в {accountSettings}. Вижте {docs} за повече информация.",
"certificateExpiryNotificationHelp": "Броят дни напред, може да се конфигурира в настройките.",
"domainExpiryNotificationHelp": "Броят дни, напред може да се конфигурира в настройките."
"domainExpiryNotificationHelp": "Броят дни, напред може да се конфигурира в настройките.",
"signalUseTemplate": "Използвай персонализиран шаблон за съобщение",
"signalUseTemplateDescription": "Ако е активирано, съобщението ще бъде изпратено с помощта на персонализиран шаблон. Можете да използвате Liquid templating, за да персонализирате формата на известията.",
"monitorTypeGameServer": "Game сървър",
"monitorTypeDatabase": "Mонитор за база данни",
"monitorTypeSpecial": "Специален",
"360messengerGroupId": "360messenger ID на група",
"360messengerUseTemplate": "Използвай персонализиран шаблон за съобщение",
"360messengerGroupList": "WhatsApp групи",
"360messengerSelectGroupList": "Изберете група за добавяне",
"360messengerSelectedGroupID": "Избрани ID-та за група",
"360messengerEnableSendToGroup": "Активиране на изпращане до група(и) в WhatsApp",
"360messengerMessageTemplate": "Шаблон за съобщение",
"360messengerErrorNoApiKey": "Моля, първо въведете вашия 360messenger API ключ.",
"360messengerErrorNoGroups": "Не са намерени групи в WhatsApp за този акаунт.",
"360messengerErrorApi": "Не може да се зареди списъкът с групи в WhatsApp (Грешка {statusCode}: {message}).",
"360messengerErrorGeneric": "Не може да се зареди груповият списъкът в WhatsApp: {message}",
"360messengerAuthToken": "360messenger API ключ",
"360messengerRecipient": "Телефонен(ни) номер(а) на получателя",
"360messengerTemplate": "360messenger шаблон за съобщение",
"360messengerCustomMessageTemplate": "Шаблон за персонализирано съобщение",
"360messengerEnableCustomMessage": "Активирай персонализиран шаблон за съобщение вместо съобщението по подразбиране.",
"360messengerWayToGetUrlAndToken": "Можете да получите вашия API ключ за 360messenger от {0}.",
"360messengerWayToWriteRecipient": "Въведете един или повече телефонни номера в международен формат без водещ плюс (напр. {0}). Разделете отделните номера със запетая.",
"GlobalpingMultipleLocationsError": "Не се поддържат множество местоположения, моля, използвайте едно местоположение за всеки монитор.",
"GlobalpingLocationDescription": "Полето за местоположение приема континенти, държави, региони, градове, ASN, интернет доставчици или облачни региони. Можете да комбинирате филтри с {plus} (напр. {amazonPlusGermany} или {comcastPlusCalifornia}). Ако латентността е важен показател, използвайте филтри, за да стесните местоположението до малък регион, за да избегнете пикове, и за по-добра стабилност задайте филтъра {datacenter}. {fullDocs}.",
"fluxerMessageFormat": "Формат на съобщението",
"fluxerMessageFormatNormal": "Нормално (с вграден rich)",
"fluxerMessageFormatCustom": "Персонализиран шаблон",
"fluxerUseMessageTemplate": "Използвай персонализиран шаблон за съобщение",
"fluxerMessageTemplate": "Шаблон за съобщение",
"Fluxer Webhook URL": "Fluxer URL адрес за уебкука",
"fluxerMessageFormatMinimalist": "Минималистичен (кратък статус)",
"fluxerUseMessageTemplateDescription": "Ако е активирано, съобщението ще бъде изпратено с помощта на персонализиран шаблон (LiquidJS). Оставете празно, за да използвате Uptime Kuma формат, който е по подразбиране.",
"wayToGetFluxerURL": "Можете да получите, като отидете в настройките на целевия канал > Уеб куки > Създаване на уеб кука > Копиране на URL адрес на уеб кука."
}

View File

@@ -73,7 +73,7 @@
"Delete": "Vymazat",
"Current": "Aktuální",
"Uptime": "Doba provozu",
"Cert Exp.": "Platnost certifikátu",
"Cert Exp.": "Expirace Cert.",
"Monitor": "Dohled | Dohledů",
"day": "den | dny/í",
"-day": "-dní",
@@ -312,7 +312,7 @@
"lineDevConsoleTo": "Konzole Line Developers - {0}",
"Basic Settings": "Obecné nastavení",
"User ID": "ID uživatele",
"Messaging API": "Messaging API",
"Messaging API": "API pro zasílání zpráv",
"wayToGetLineChannelToken": "Nejprve otevřete {0}, vytvořte poskytovatele a kanál (Messaging API). Poté můžete získat přístupový token ke kanálu a ID uživatele, v sekci uvedené výše.",
"Icon URL": "URL adresa ikony",
"aboutIconURL": "Pro přepsání výchozího profilového obrázku můžete do pole \"URL adresa ikony\" zadat odkaz na obrázek. Nebude použito, pokud je nastavena ikona smajlíka.",
@@ -816,7 +816,7 @@
"AccessKey Id": "AccessKey Id",
"Session Token": "Token relace",
"Pick a SASL Mechanism...": "Vyberte SASL mechanismus…",
"Secret AccessKey": "Secret AccessKey",
"Secret AccessKey": "Heslo AccessKey",
"Server URL should not contain the nfty topic": "URL serveru by neměla obsahovat nfty vlákno",
"Kafka SASL Options": "Možnosti Kafka SASL",
"Enable Kafka Producer Auto Topic Creation": "Povolit Kafka zprostředkovateli automatické vytváření vláken",
@@ -871,7 +871,7 @@
"monitorToastMessagesLabel": "Upozornění Monitor Toast",
"monitorToastMessagesDescription": "Upozornění Toast zmizí po uplynutí nastaveného času. Časový limit vypnete nastavením -1. Upozornění vypnete nastavením 0.",
"pushViewCode": "Jak používat Push monitor? (Zobrazit kód)",
"liquidIntroduction": "Šablony je možné vytvářet pomocí jazyka Liquid. Pokyny k použití naleznete v {0}. Toto jsou dostupné proměnné:",
"liquidIntroduction": "Šablonovatelnost je dosažena pomocí šablonovacího jazyka Liquid. Pokyny k použití naleznete v {0}.",
"templateMsg": "zpráva upozornění",
"emailCustomisableContent": "Přizpůsobitelný obsah",
"GrafanaOncallUrl": "URL pro volání do Grafana",
@@ -1131,7 +1131,7 @@
"telegramServerUrlDescription": "Pro zrušení omezení api botů Telegramu nebo získání přístupu v blokovaných oblastech (Čína, Írán, atp.). Pro více informací klikněte na {0}. Výchozí nastavení: {1}",
"Use HTML for custom E-mail body": "Použít HTML v těle vlastního e-mailu",
"ntfyPriorityHelptextPriorityHigherThanDown": "Pravidelná priorita by měla být vyšší než priorita {0}. Priorita {1} je vyšší než {0} s prioritou {2}",
"OAuth Audience": "OAuth Audience",
"OAuth Audience": "Cílová skupina OAuth",
"Optional: The audience to request the JWT for": "Volitelné: Audience, pro které se má vyžádat JWT",
"pingCountDescription": "Počet paketů, které se před zastavením odešlou",
"pingNumericDescription": "Pokud je tato možnost aktivní, místo symbolických názvů hostitelů se zobrazí IP adresy",
@@ -1140,7 +1140,7 @@
"wayToGetWahaSession": "Z této relace WAHA odesílá oznámení do ID chatu. Najdete ho v nástěnce WAHA.",
"wayToWriteWahaChatId": "Telefonní číslo s mezinárodní předvolbou, ale bez znaménka plus na začátku ({0}), ID kontaktu ({1}) nebo ID skupiny ({2}). Oznámení jsou z relace WAHA odesílána na toto ID chatu.",
"telegramServerUrl": "(Volitelné) Adresa serveru",
"YZJ Robot Token": "YZJ Robot token",
"YZJ Robot Token": "Token robota YZJ",
"YZJ Webhook URL": "YZJ URL webhooku",
"Add Tags": "Přidat štítky",
"tagAlreadyOnMonitor": "Dohled již má přiřazen tento štítek (název a hodnota) nebo se čeká na jeho přidání.",
@@ -1198,7 +1198,7 @@
"wayToGetEvolutionUrlAndToken": "API URL a token získáte tak, že přejdete do požadovaného kanálu z {0}",
"Nextcloud host": "Nextcloud hostitel",
"Conversation token": "Token konverzace",
"Bot secret": "Bot secret",
"Bot secret": "Heslo bota",
"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.",
@@ -1369,7 +1369,7 @@
"halopsa_webhook_url_desc": "Zadejte URL adresu webhooku z vašeho Runbooku pro integraci Halo PSA (Konfigurace > Integrace > Vlastní integrace > Runbooky pro integraci). Při vytváření webhooku vyberte možnost 'Lze spustit pouze z Halo a z veřejného koncového bodu'.",
"halopsa_password_desc": "Heslo pro ověření Halo PSA webhooku",
"Webpush Helptext": "Web push funguje pouze při použití SSL (HTTPS) spojení. Na iOS zařízeních je nutné webovou stránku nejprve přidat na domovskou obrazovku.",
"labelDomainExpiry": "Platnost domény",
"labelDomainExpiry": "Expirace Domény",
"settingsDomainExpiry": "Platnost domény",
"labelDomainNameExpiryNotification": "Oznámení na blížící se konec platnosti doménového jména",
"domainExpiryDescription": "Upozornit, pokud platnost doménového jména končí za:",
@@ -1378,7 +1378,7 @@
"domain_expiry_public_suffix_too_short": "\".{publicSuffix}\" je pro doménu nejvyšší úrovně (TLD) příliš krátké",
"lowIntervalWarning": "Opravdu chcete nastavit hodnotu intervalu pod 20 sekund? Může dojít ke snížení výkonu, zejména při velkém počtu dohledů.",
"imageResetConfirmation": "Obnovení výchozího nastavení obrázku",
"domain_expiry_unsupported_is_icann": "Doména \"{domain}\" není kandidátem pro sledování konce platnosti domény, protože její veřejná přípona \"{.publicSuffix}“ nepatří ICAN",
"domain_expiry_unsupported_is_icann": "Doména {domain} není kandidátem pro dohled nad vypršením platnosti domény, protože její veřejná přípona „.{publicSuffix}“ není spravována organizací ICANN",
"Unable to get permission to notify": "Nepodařilo se získat oprávnění pro odeslání upozornění (žádost byla buď zamítnuta, nebo ignorována).",
"domain_expiry_unsupported_unsupported_tld_no_rdap_endpoint": "Sledování konce platnosti domény není dostupné pro \".{publicSuffix}\" domény, protože IANA neposkytuje žádnou RDAP službu",
"halopsa_username_desc": "Uživatelské jméno pro ověření Halo PSA webhooku",
@@ -1413,7 +1413,7 @@
"None (Successful Connection)": "Žádné (úspěšné spojení)",
"TLS Alert Spec": "RFC 8446",
"Never": "Nikdy",
"playground": "playground",
"playground": "hřiště",
"Severity": "Závažnost",
"expectedTlsAlertDescription": "Vyberte upozornění TLS, která má server vracet. Pomocí kódu {code} ověřte, zda mTLS koncové body zamítají spojení bez klientských certifikátů. Podrobnosti naleznete na odkaze {link}.",
"Region": "Region",
@@ -1474,5 +1474,98 @@
"Quick Setup Guide": "Stručný návod",
"Pinned incidents are shown prominently on the status page": "Připnuté mimořádné události jsou předním obsahem stavové stránky",
"Edit Incident": "Upravit mimořádnou událost",
"Paste the script code (see below)": "Vložte kód sem (vizte níže)"
"Paste the script code (see below)": "Vložte kód sem (vizte níže)",
"monitorTypeGameServer": "Herní server",
"slug is not found": "Slug nebyl nalezen",
"Cloud ID": "Cloud ID",
"certificateExpiryNotificationHelp": "Počet dní předem lze nastavit v nastavení.",
"aboutJiraCloudId": "Další informace o Jira Cloud ID: {0}",
"Click Deploy → New deployment → Web app": "Klikněte na Nasadit → Nové nasazení → Webová aplikace",
"matrixUseTemplateDescription": "Pokud je tato možnost povolena, bude zpráva odeslána pomocí vlastní šablony.",
"signalUseTemplateDescription": "Pokud je tato možnost povolena, bude zpráva odeslána pomocí vlastní šablony. K přizpůsobení formátu oznámení můžete použít šablony Liquid.",
"slackIncludeGroupName": "Zahrnout název skupiny dohledů",
"signalUseTemplate": "Použít vlastní šablonu zprávy",
"monitorTypeDatabase": "Typ monitorování databáze",
"monitorTypeSpecial": "Speciální",
"Google Apps Script Code": "Kód skriptu Google Apps",
"Jira Service Management": "Správa služeb Jira",
"Go to Extensions → Apps Script": "Přejdi do Rozšíření → Skript aplikací",
"Set 'Execute as: Me' and 'Who has access: Anyone'": "Nastavte „Spustit jako: Já“ a „Kdo má přístup: Kdokoli“",
"Copy the web app URL and paste it above": "Zkopírujte adresu URL webové aplikace a vložte ji výše",
"Teltonika SMS Gateway": "SMS brána Teltonika",
"teltonikaVersionWarning": "Tento poskytovatel oznámení vyžaduje, aby vaše zařízení Teltonika používalo RMS verze 7.14.0 nebo vyšší.",
"360messengerUseTemplate": "Použít vlastní šablonu zprávy",
"360messengerAuthToken": "Klíč API 360messenger",
"360messengerRecipient": "Číslo/čísla telefonu příjemce",
"360messengerGroupId": "360messenger ID skupiny",
"360messengerTemplate": "Šablona zprávy 360messenger",
"360messengerGroupList": "Skupiny WhatsApp",
"360messengerSelectGroupList": "Vyberte skupinu, kterou chcete přidat",
"360messengerSelectedGroupID": "Vybrané ID skupiny (skupin)",
"360messengerEnableSendToGroup": "Povolit odesílání do skupin WhatsApp",
"360messengerCustomMessageTemplate": "Vlastní šablona zprávy",
"360messengerMessageTemplate": "Šablona zprávy",
"360messengerWayToGetUrlAndToken": "Klíč API pro 360messenger získáte na adrese {0}.",
"360messengerWayToWriteRecipient": "Zadejte jedno nebo více telefonních čísel v mezinárodním formátu bez úvodního znaménka plus (např. {0}). Více čísel oddělte čárkami.",
"360messengerErrorNoApiKey": "Nejprve zadejte svůj klíč API 360messenger.",
"360messengerErrorNoGroups": "Pro tento účet nebyly nalezeny žádné skupiny WhatsApp.",
"360messengerErrorApi": "Nelze načíst seznam skupin WhatsApp (Chyba {statusCode}: {message}).",
"360messengerErrorGeneric": "Nelze načíst seznam skupin WhatsApp: {message}",
"teltonikaUsernameHelptext": "Doporučení: Vytvořte samostatný účet, který bude omezen pouze na odesílání SMS zpráv, a zadejte zde jeho uživatelské jméno",
"teltonikaPassword": "Heslo API",
"teltonikaPasswordHelptext": "Heslo uživatele API můžete definovat ve svém routeru Teltonika, např. {0}",
"teltonikaModem": "ID modemu",
"teltonikaPhoneNumber": "Telefonní číslo",
"discordMessageFormat": "Formát zprávy",
"discordMessageFormatNormal": "Normální (bohaté vkládání)",
"discordMessageFormatMinimalist": "Minimalistický (krátký stav)",
"discordMessageFormatCustom": "Vlastní šablona",
"discordUseMessageTemplateDescription": "Pokud je tato možnost povolena, bude zpráva odeslána pomocí vlastní šablony (LiquidJS). Nechte pole prázdné, pokud chcete použít výchozí formát Uptime Kuma.",
"discordMessageTemplate": "Šablona zprávy",
"discordUseMessageTemplate": "Použít vlastní šablonu zprávy",
"GlobalpingLocationDescription": "Do pole pro zadání umístění lze zadat kontinenty, země, regiony, města, ASN, ISP nebo cloudové regiony. Filtry lze kombinovat pomocí znaku {plus} (např. {amazonPlusGermany} nebo {comcastPlusCalifornia}). Pokud je latence důležitým měřítkem, použijte filtry k zúžení umístění na malý region, abyste se vyhnuli výkyvům, a pro lepší stabilitu nastavte filtr {datacenter}. {fullDocs}.",
"GlobalpingMultipleLocationsError": "Více umístění není podporováno, pro každý dohled použijte jedno umístění.",
"halopsa_payload_desc": "Následující pole jsou odeslána do vašeho webhooku Halo PSA:",
"halopsa_field_title": "Název upozornění (vždy „Uptime Kuma upozornění“)",
"halopsa_field_status": "Stav dohledu: AKTIVNÍ, NEAKTIVNÍ, OZNÁMENÍ nebo NEZNÁMÝ",
"halopsa_field_monitor": "Název dohledu",
"halopsa_field_message": "Úplná výstražná zpráva se stavem a podrobnostmi",
"halopsa_field_timestamp": "Časové razítko události ve formátu ISO 8601",
"teltonikaModemHelptext": "ID SMS modemu musí být ve formátu {0}. Pokyny najdete na stránce https://developers.teltonika-networks.com/reference/.",
"halopsa_field_uptime_kuma_version": "Číslo verze Uptime Kuma",
"360messengerEnableCustomMessage": "Povolit vlastní šablonu zprávy namísto výchozí zprávy.",
"GlobalpingMonitorDescription": "Globalping poskytuje přístup k tisícům komunitních sond pro provádění síťových testů a měření. Pro všechny anonymní uživatele je stanoven limit 250 testů za hodinu. Chcete-li tento limit zdvojnásobit na 500 testů za hodinu, uložte si svůj token v {accountSettings}. Další informace najdete v {docs}.",
"halopsa_field_monitor_id": "Jedinečný identifikátor dohledu (null pro testovací oznámení) použijte k přiřazení upozornění k ticketům",
"globalpingApiTokenDescription": "Získejte svůj token API Globalping na {0}.",
"slackIncludeGroupNameDescription": "Pokud je tato možnost povolena, bude do oznámení zahrnuta cesta ke skupině dohledů, aby bylo možné rozlišit dohledy se stejným názvem v různých skupinách.",
"slackUseTemplate": "Použít vlastní šablonu zprávy",
"Webhook Payload Fields": "Pole datové části webhooku",
"slackUseTemplateDescription": "Pokud je tato možnost povolena, zpráva bude odeslána pomocí vlastní šablony. Pomocí šablon Liquid můžete zahrnout informace o skupině dohledů prostřednictvím monitorJSON.path nebo monitorJSON.pathName.",
"Globalping - Access global monitoring probes": "Globalping přístup k globálním monitorovacím sondám",
"GlobalpingHostname": "Veřejně dostupný cíl měření. Obvykle se jedná o název hostitele nebo adresu IPv4/IPv6, v závislosti na typu měření.",
"account settings": "nastavení účtu",
"GlobalpingResolverInfo": "Adresa IPv4/IPv6 nebo plně kvalifikovaný název domény (FQDN). Výchozí nastavení je lokální síťový resolver sondy. Server resolveru můžete kdykoli změnit.",
"RecordMatch": "Porovnání rekordní hodnoty",
"RegexMatch": "Zadejte regulární výraz, který porovná hodnoty záznamu",
"Protocol": "Protokol",
"domainExpiryNotificationHelp": "Počet dní předem lze nastavit v nastavení.",
"halopsa_id_usage_hint": "💡 Tip: Použijte monitor_id pro spolehlivé přiřazení upozornění k ticketům a heartbeat_id pro sledování historie událostí",
"halopsa_setup_step5": "Nakonfigurujte runbook tak, aby používal monitor_id pro přiřazování výstrah k existujícím ticketům",
"teltonikaUrl": "URL vašeho zařízení Teltonika",
"teltonikaUrlHelptext": "URL by měla být zadána jako úplný původ, např. {0} nebo {1}.",
"teltonikaUnsafeTls": "Ignorovat ověření certifikátu",
"teltonikaUnsafeTlsDescription": "Vypnutím ověřování certifikátů TLS se vystavujete riziku útoků typu „man-in-the-middle“, které mohou vést k úniku dat a převzetí kontroly nad systémy. Ověřování certifikátů nevypínejte, pokud nechcete přijmout riziko tohoto typu útoku. Doporučujeme používat LetsEncrypt s automatickým obnovováním.",
"teltonikaUsername": "API uživatelské jméno",
"teltonikaPhoneNumberHelptext": "Číslo musí být v mezinárodním formátu {0}, {1}. Je povoleno pouze jedno číslo.",
"Globalping API Token": "Token API Globalping",
"GlobalpingLocationDocs": "Kompletní dokumentace k zadávání polohy",
"GlobalpingIpFamilyInfo": "Verze IP, která se má použít. Povoleno pouze v případě, že cílem je název hostitele.",
"Location": "Umístění",
"Monitor Subtype": "Podtyp dohledu",
"Check for": "Zkontrolujte",
"ntfyUseTemplate": "Přizpůsobit šablony oznámení",
"ntfyUseTemplateDescription": "Povolit tuto funkci pro přizpůsobení názvů a zpráv oznámení pomocí šablon LiquidJS",
"ntfyCustomTitle": "Vlastní šablona názvu",
"ntfyCustomMessage": "Šablona vlastní zprávy",
"ntfyNotificationTemplateFallback": "Nechte prázdné pole, pokud chcete použít výchozí formát Uptime Kuma"
}

View File

@@ -1579,7 +1579,14 @@
"teltonikaVersionWarning": "Dieser Benachrichtigungsanbieter erfordert, dass auf deinem Teltonika-Gerät RMS-Version 7.14.0 oder höher installiert ist.",
"teltonikaUnsafeTlsDescription": "Die Deaktivierung der TLS Zertifikatsprüfung setzt dich möglichen On-Path-Angriffen (Man in the Middle-Attacken) aus, welche zu Datenlecks und Systemübernahmen führen können. Deaktiviere diese Prüfung nur, wenn du den Angriffsvektor akzeptierst. Wir empfehlen die Verwendung von Let's Encrypt mit automatischer Verlängerung.",
"teltonikaModemHelptext": "Die ID des SMS-Modems muss das Format {0} haben. Weitere Informationen kannst du unter https://developers.teltonika-networks.com/reference/ finden.",
"RecordMatch": "Datensatzwert-Übereinstimmung",
"RegexMatch": "Gib einen regulären Ausdruck ein, der mit dem Datensatzwert übereinstimmt",
"GlobalpingMonitorDescription": "Globalping bietet Zugriff auf Tausende von Community-gehosteten Sonden, um Netzwerktests und -messungen durchzuführen. Für alle anonymen Benutzer gilt eine Begrenzung von 250 Tests pro Stunde. Um die Begrenzung auf 500 pro Stunde zu verdoppeln, speichere bitte dein Token in {accountSettings}. Weitere Informationen findest du in den {docs}."
"RecordMatch": "Eintragswert-Übereinstimmung",
"RegexMatch": "Gib einen regulären Ausdruck ein, der mit dem Eintragswert übereinstimmt",
"GlobalpingMonitorDescription": "Globalping bietet Zugriff auf Tausende von Community-gehosteten Sonden, um Netzwerktests und -messungen durchzuführen. Für alle anonymen Benutzer gilt eine Begrenzung von 250 Tests pro Stunde. Um die Begrenzung auf 500 pro Stunde zu verdoppeln, speichere bitte dein Token in {accountSettings}. Weitere Informationen findest du in den {docs}.",
"certificateExpiryNotificationHelp": "Die Anzahl der Tage im Voraus kann in den Einstellungen konfiguriert werden.",
"domainExpiryNotificationHelp": "Die Anzahl der Tage im Voraus kann in Einstellungen konfiguriert werden.",
"signalUseTemplate": "Benutzerdefinierte Nachrichtenvorlage verwenden",
"signalUseTemplateDescription": "Wenn diese Option aktiviert ist, wird die Nachricht mit einer benutzerdefinierten Vorlage versendet. Du kannst Liquid-Templating verwenden, um das Benachrichtigungsformat anzupassen.",
"monitorTypeGameServer": "Spieleserver",
"monitorTypeDatabase": "Datenbankmonitor-Typ",
"monitorTypeSpecial": "Speziell"
}

View File

@@ -39,6 +39,9 @@
"General Monitor Type": "General Monitor Type",
"Passive Monitor Type": "Passive Monitor Type",
"Specific Monitor Type": "Specific Monitor Type",
"monitorTypeGameServer": "Game Server",
"monitorTypeDatabase": "Database Monitor Type",
"monitorTypeSpecial": "Special",
"markdownSupported": "Markdown syntax supported. If using HTML, avoid leading spaces to prevent formatting issues.",
"pauseDashboardHome": "Pause",
"Pause": "Pause",
@@ -511,9 +514,9 @@
"Home Assistant URL": "Home Assistant URL",
"Long-Lived Access Token": "Long-Lived Access Token",
"Long-Lived Access Token can be created by clicking on your profile name (bottom left) and scrolling to the bottom then click Create Token. ": "Long-Lived Access Token can be created by clicking on your profile name (bottom left) and scrolling to the bottom then click Create Token.",
"Notification Service": "Notification Service",
"Notification Action": "Notification Action",
"default: notify all devices": "default: notify all devices",
"A list of Notification Services can be found in Home Assistant under \"Developer Tools > Services\" search for \"notification\" to find your device/phone name.": "A list of Notification Services can be found in Home Assistant under \"Developer Tools > Services\" search for \"notification\" to find your device/phone name.",
"homeAssistantNotificationActionHelptext": "A list of Notification Actions can be found in Home Assistant under \"Settings > Developer Tools > Actions\". Search for \"notify\" to find your actions. Enter only the part after \"notify.\", e.g. for the action \"notify.mobile_app_xyz\" enter \"mobile_app_xyz\". For built-in mobile notifications, look for \"Send a notification via mobile_app_xyz\" (not \"Send a notification\").",
"Automations can optionally be triggered in Home Assistant:": "Automations can optionally be triggered in Home Assistant:",
"Trigger type:": "Trigger type:",
"Event type:": "Event type:",
@@ -863,6 +866,8 @@
"Remove domain": "Remove domain '{0}'",
"Icon Emoji": "Icon Emoji",
"signalImportant": "IMPORTANT: You cannot mix groups and numbers in recipients!",
"signalUseTemplate": "Use custom message template",
"signalUseTemplateDescription": "If enabled, the message will be sent using a custom template. You can use Liquid templating to customize the notification format.",
"aboutWebhooks": "More info about Webhooks on: {0}",
"aboutJiraCloudId": "More info about Jira Cloud ID: {0}",
"see Jira Cloud Docs": "see Jira Cloud Docs",
@@ -1309,6 +1314,24 @@
"wayToGetWahaApiKey": "API Key is WHATSAPP_API_KEY environment variable value you used to run WAHA.",
"wayToGetWahaSession": "From this session WAHA sends notifications to Chat ID. You can find it in WAHA Dashboard.",
"wayToWriteWahaChatId": "The phone number with the international prefix, but without the plus sign at the start ({0}), the Contact ID ({1}) or the Group ID ({2}). Notifications are sent to this Chat ID from WAHA Session.",
"360messengerAuthToken": "360messenger API Key",
"360messengerRecipient": "Recipient phone number(s)",
"360messengerGroupId": "360messenger Group ID",
"360messengerUseTemplate": "Use a custom message template",
"360messengerTemplate": "360messenger Message Template",
"360messengerGroupList": "WhatsApp groups",
"360messengerSelectGroupList": "Select a group to add",
"360messengerSelectedGroupID": "Selected Group ID(s)",
"360messengerEnableSendToGroup": "Enable sending to WhatsApp group(s)",
"360messengerCustomMessageTemplate": "Custom message template",
"360messengerEnableCustomMessage": "Enable a custom message template instead of the default message.",
"360messengerMessageTemplate": "Message template",
"360messengerWayToGetUrlAndToken": "You can get your 360messenger API key from {0}.",
"360messengerWayToWriteRecipient": "Enter one or more phone numbers in international format without a leading plus (e.g. {0}). Separate multiple numbers with commas.",
"360messengerErrorNoApiKey": "Please enter your 360messenger API key first.",
"360messengerErrorNoGroups": "No WhatsApp groups were found for this account.",
"360messengerErrorApi": "Unable to load the WhatsApp group list (Error {statusCode}: {message}).",
"360messengerErrorGeneric": "Unable to load the WhatsApp group list: {message}",
"YZJ Webhook URL": "YZJ Webhook URL",
"YZJ Robot Token": "YZJ Robot token",
"Plain Text": "Plain Text",
@@ -1335,6 +1358,15 @@
"discordUseMessageTemplate": "Use custom message template",
"discordUseMessageTemplateDescription": "If enabled, the message will be sent using a custom template (LiquidJS). Leave blank to use the default Uptime Kuma format.",
"discordMessageTemplate": "Message Template",
"fluxerMessageFormat": "Message Format",
"fluxerMessageFormatNormal": "Normal (rich embeds)",
"fluxerMessageFormatMinimalist": "Minimalist (short status)",
"fluxerMessageFormatCustom": "Custom template",
"fluxerUseMessageTemplate": "Use custom message template",
"fluxerUseMessageTemplateDescription": "If enabled, the message will be sent using a custom template (LiquidJS). Leave blank to use the default Uptime Kuma format.",
"fluxerMessageTemplate": "Message Template",
"Fluxer Webhook URL": "Fluxer Webhook URL",
"wayToGetFluxerURL": "You can get this by going to the target channel's settings > Webhooks > Create Webhook > Copy Webhook URL.",
"Ip Family": "IP Family",
"ipFamilyDescriptionAutoSelect": "Uses the {happyEyeballs} for determining the IP family.",
"Happy Eyeballs algorithm": "Happy Eyeballs algorithm",
@@ -1354,8 +1386,9 @@
"Globalping API Token": "Globalping API Token",
"globalpingApiTokenDescription": "Get your Globalping API Token at {0}.",
"GlobalpingHostname": "A publicly reachable measurement target. Typically a hostname or IPv4/IPv6 address, depending on the measurement type.",
"GlobalpingLocation": "The location field accepts continents, countries, regions, cities, ASNs, ISPs, or cloud regions. You can combine filters with {plus} (e.g {amazonPlusGermany} or {comcastPlusCalifornia}). If latency is an important metric, use filters to narrow down the location to a small region to avoid spikes. {fullDocs}.",
"GlobalpingLocationDescription": "The location field accepts continents, countries, regions, cities, ASNs, ISPs, or cloud regions. You can combine filters with {plus} (e.g {amazonPlusGermany} or {comcastPlusCalifornia}). If latency is an important metric, use filters to narrow down the location to a small region to avoid spikes and for better stability set the {datacenter} filter. {fullDocs}.",
"GlobalpingLocationDocs": "Full location input documentation",
"GlobalpingMultipleLocationsError": "Multiple locations are not supported, please use a single location for each monitor.",
"GlobalpingIpFamilyInfo": "The IP version to use. Only allowed if the target is a hostname.",
"GlobalpingResolverInfo": "IPv4/IPv6 address or a fully Qualified Domain Name (FQDN). Defaults to the probe's local network resolver. You can change the resolver server anytime.",
"RecordMatch": "Record value match",
@@ -1383,9 +1416,7 @@
"domainExpiryDescription": "Trigger notification when domain names expires in:",
"domain_expiry_unsupported_monitor_type": "Domain expiry monitoring is not supported for this monitor type",
"domain_expiry_unsupported_missing_target": "No valid domain or hostname is configured for this monitor",
"domain_expiry_public_suffix_too_short": "\".{publicSuffix}\" is too short for a top level domain",
"domain_expiry_unsupported_is_icann": "The domain \"{domain}\" is not a candidate for domain expiry monitoring, because its public suffix \".{publicSuffix}\" is not ICAN",
"domain_expiry_unsupported_is_ip": "\"{hostname}\" is an IP address. Domain expiry monitoring requires a domain name",
"domain_expiry_unsupported_is_icann": "The domain \"{domain}\" is not a candidate for domain expiry monitoring, because its public suffix \".{publicSuffix}\" is not managed by ICANN",
"domain_expiry_unsupported_unsupported_tld_no_rdap_endpoint": "Domain expiry monitoring is not available for \".{publicSuffix}\" because no RDAP service is listed by IANA",
"minimumIntervalWarning": "Intervals below 20 seconds may result in poor performance.",
"lowIntervalWarning": "Are you sure want to set the interval value below 20 seconds? Performance may be degraded, particularly if there are a large number of monitors.",

1
src/lang/en_GB.json Normal file
View File

@@ -0,0 +1 @@
{}

View File

@@ -155,7 +155,7 @@
"Skip existing": "Omitir existente",
"Overwrite": "Sobrescribir",
"Options": "Opciones",
"Keep both": "Manténer ambos",
"Keep both": "Mantener ambos",
"Tags": "Etiquetas",
"Add New below or Select...": "Agregar nuevo a continuación o seleccionar…",
"Tag with this name already exist.": "Una etiqueta con este nombre ya existe.",
@@ -510,7 +510,7 @@
"Octopush API Version": "Versión API Octopush",
"From Name/Number": "De Nombre/Número",
"Recipient Number": "Número de Destinatario",
"Long-Lived Access Token can be created by clicking on your profile name (bottom left) and scrolling to the bottom then click Create Token. ": "El token de acceso de larga duración se puede crear haciendo clic en el nombre de su perfil (abajo a la izquierda) y desplazándose hasta la parte inferior y luego haciendo clic en Crear token. ",
"Long-Lived Access Token can be created by clicking on your profile name (bottom left) and scrolling to the bottom then click Create Token. ": "El token de acceso de larga duración puede crearse haciendo clic en el nombre de su perfil (abajo a la izquierda), desplazándose hasta la parte inferior y luego haciendo clic en Crear token.",
"backupOutdatedWarning": "Obsoleto: dado que se agregaron muchas funciones y esta función de copia de seguridad no se mantiene desde hace un tiempo, no puede generar ni restaurar una copia de seguridad completa.",
"Optional": "Opcional",
"loadingError": "No se pueden obtener los datos, inténtelo de nuevo más tarde.",
@@ -597,7 +597,7 @@
"checkPrice": "Consultar {0} precios:",
"apiCredentials": "Credenciales de API",
"Check octopush prices": "Consulta los precios de octopush {0}.",
"octopushPhoneNumber": "Número de teléfono (en formato internacional, ejemplo: +33612345678) ",
"octopushPhoneNumber": "Número de teléfono (en formato internacional, ejemplo: +33612345678)",
"octopushSMSSender": "Nombre de Remitente del SMS: 3-11 caracteres alfanuméricos y espacio (a-zA-Z0-9)",
"LunaSea Device ID": "ID Dispositivo LunaSea",
"goAlert": "GoAlert",
@@ -649,7 +649,7 @@
"alertaEnvironment": "Entorno",
"PushDeer Key": "Key de PushDeer",
"onebotSafetyTips": "Por seguridad, deberías colocara el token de acceso",
"wayToGetClickSendSMSToken": "Puedes obtener Nombre de Usuario de la API y la llave {aquí}.",
"wayToGetClickSendSMSToken": "Puedes obtener Usuario de API y llave de API {aquí}.",
"Apprise URL": "URL Apprise",
"gorush": "Gorush",
"squadcast": "Squadcast",
@@ -680,7 +680,7 @@
"smseagleGroup": "Nombre(s) de grupo(s) de Guía Telefónica",
"Unpin": "Dejar de Fijar",
"Prefix Custom Message": "Prefijo personalizado",
"markdownSupported": "Sintaxis de Markdown soportada",
"markdownSupported": "Sintaxis de Markdown soportada. Si estas usando HTML, evita espacios al principio para prevenir problemas de formato.",
"Server Address": "Dirección del Servidor",
"Learn More": "Aprende Más",
"Pick a RR-Type...": "Seleccione un Tipo RR…",
@@ -846,7 +846,7 @@
"toastSuccessTimeout": "Tiempo de espera para notificaciones de éxito",
"toastErrorTimeout": "Tiempo de espera para notificaciones de error",
"setupDatabaseChooseDatabase": "¿Qué base de datos te gustaría usar?",
"setupDatabaseEmbeddedMariaDB": "No necesitas configurar nada. Esta imagen de Docker tiene incorporada y configurada MariaDB automáticamente para ti. Uptime Kuma se conectará a esta base de datos a través de un socket Unix.",
"setupDatabaseEmbeddedMariaDB": "No necesitas configurar nada. Esta imagen de Docker tiene incorporado y configurado MariaDB para ti automáticamente. Uptime Kuma se conectará a esta base de datos a través de un socket Unix.",
"setupDatabaseMariaDB": "Conectarse a una base de datos MariaDB externa. Debe configurar la información de conexión a la base de datos.",
"setupDatabaseSQLite": "Un archivo de base de datos simple, recomendado para despliegues a pequeña escala. Antes de la versión 2.0.0, Uptime Kuma utilizaba SQLite como base de datos predeterminada.",
"dbName": "Nombre de la Base de Datos",
@@ -854,7 +854,7 @@
"authIncorrectCreds": "Nombre de usuario o contraseña incorrectos.",
"2faEnabled": "2FA habilitado.",
"2faDisabled": "2FA deshabilitado.",
"liquidIntroduction": "La plantilla se logra a través del lenguaje de plantillas Liquid. Consulte {0} para obtener instrucciones de uso. Estas son las variables disponibles:",
"liquidIntroduction": "El plantillaje se logra a través del lenguaje de plantillas Liquid. Consulte {0} para obtener instrucciones de uso.",
"templateLimitedToUpDownCertNotifications": "solo disponible para notificaciones FUNCIONAL/CAÍDO/Caducidad de certificado",
"emailTemplateMsg": "mensaje de la notificación",
"emailTemplateLimitedToUpDownNotification": "sólo disponible para latidos FUNCIONAL/CAÍDO, de lo contrario nulo",
@@ -939,7 +939,7 @@
"threemaSenderIdentity": "ID de Gateway",
"threemaSenderIdentityFormat": "8 caracteres, generalmente comienza con *",
"Host URL": "URL del anfitrión",
"Either enter the hostname of the server you want to connect to or localhost if you intend to use a locally configured mail transfer agent": "Ingresa el nombre del host del servidor al que deseas conectarte, o {localhost} si deseas usar un {local_mta}",
"Either enter the hostname of the server you want to connect to or localhost if you intend to use a locally configured mail transfer agent": "Ingresa el nombre del host del servidor al que deseas conectarte o {localhost} si deseas usar un {local_mta}",
"smspartnerPhoneNumberHelptext": "El número debe estar en el formato internacional {0}, {1}. Múltiples números deben estar separados por {2}",
"smspartnerSenderName": "Nombre del emisor del SMS",
"smspartnerApiurl": "Puedes encontrar tu clave API en tu panel de control en {0}",
@@ -1213,19 +1213,19 @@
"Number of retry attempts if webhook fails": "Número de intentos de reintento (cada 60180 segundos) si el webhook falla.",
"Maximum Retries": "Máximo de reintentos",
"sipsakPingWarning": "Para poder utilizar el monitor de SIP Options Ping, necesitas instalar Uptime Kuma sin Docker e instalar el cliente Sipsak en tu servidor.",
"Plausible": "Plausible",
"Plausible": "Admisible",
"Select All": "Seleccionar todo",
"Deselect All": "Desmarcar todo",
"HTTP Method": "Método HTTP",
"webhookPostMethodDesc": "POST es aceptado para la mayoría de servidores HTTP.",
"webhookPostMethodDesc": "POST es adecuado para la mayoría de servidores HTTP.",
"resendApiKey": "Reenviar la llave API",
"deleteGroupMsg": "¿Estás seguro de querer eliminar este grupo?",
"deleteGroupMsg": "¿Está seguro que quiere eliminar este grupo?",
"settingsDomainExpiry": "Expiración de dominio",
"labelDomainExpiry": "Exp. de dominio",
"message": "mensaje",
"domainExpiryDescription": "Lanzar notificación cuando el nombre de dominio expire en:",
"year": "año | años",
"descriptionHelpText": "Se muestra en el panel principal interno. El código Markdown está permitido y se senea (preserva los espacios y las identaciones) antes de mostrarse.",
"descriptionHelpText": "Mostrar en el panel de control interno. Se permite markdown limpio (conserva el espacio y la sangría) antes de mostrarse.",
"json_value": "Valor JSON",
"Press Enter to add node": "Pulsa Enter para añadir el nodo",
"resendApiHelp": "Crear una llave de API aquí {0}",
@@ -1235,7 +1235,7 @@
"wsCodeDescription": "Para más información acerca de los códigos de estado, por favor consulta {rfc6455}",
"Subprotocol(s)": "Subprotocolo(s)",
"certHostnameMismatch": "El nombre de host del certificado no coincide con la URL del monitor.",
"twilioMessagingServiceSID": "Servicio de mensajería SID (opcional)",
"twilioMessagingServiceSID": "SID del servicio de Mensajería (opcional)",
"resendFromEmail": "Correo electrónico del remitente",
"resendLeaveBlankForDefaultSubject": "Deja en blanco para utilizar el asunto por defecto",
"ignoreSecWebsocketAcceptHeaderDescription": "En caso de que la actualización del websocket sea satisfactoria, permite al servidor no responder con la cabecera Sec-WebSocket-Accept.",
@@ -1261,7 +1261,7 @@
"systemServiceDescriptionWindows": "Comprueba si el gestor de servicios {service_name} de Windows está ejecutándose",
"invalidURL": "URL no válida",
"Clone Maintenance": "Clonar Mantenimiento",
"ariaPauseMaintenance": "Pausar este horario de mantenimiento",
"ariaPauseMaintenance": "Pausar este cronograma de mantenimiento",
"systemServiceName": "Nombre del servicio",
"systemService": "Servicio del sistema",
"systemServiceCommandHint": "Comando utilizado: {command}",
@@ -1276,15 +1276,15 @@
"Browser not supported": "Navegador no permitido",
"labelDomainNameExpiryNotification": "Notificación de expiración de dominio",
"Duration (Minutes)": "Duración (Minutos)",
"ariaResumeMaintenance": "Reanudar este horario de mantenimiento",
"ariaCloneMaintenance": "Crear una copia de este horario de mantenimiento",
"ariaEditMaintenance": "Editar este horario de mantenimiento",
"ariaDeleteMaintenance": "Eliminar este horario de mantenimiento",
"ariaResumeMaintenance": "Reanudar este cronograma de mantenimiento",
"ariaCloneMaintenance": "Crear una copia de este cronograma de mantenimiento",
"ariaEditMaintenance": "Editar este cronograma de mantenimiento",
"ariaDeleteMaintenance": "Eliminar este cronograma de mantenimiento",
"SMTP Security": "Seguridad SMTP",
"Ignore STARTTLS": "Ignorar STARTTLS",
"Use STARTTLS": "Utilizar STARTTLS",
"twilloMessagingServiceSIDHelptext": "Introduce el SID de tu servicio de mensajería si utilizas {twillo_messaging_service_help_link} para genstionar los remitentes y las características",
"webhookGetMethodDesc": "GET envía los datos como parámetros de la búsqueda y no permite configurar un cuerpo de mensaje. Útil para disparar los monitores Push de Uptime Kuma.",
"twilloMessagingServiceSIDHelptext": "Ingrese el SID del Servicio de Mensajería aquí si está usando {twillo_messaging_service_help_link} para administrar los remitentes y características",
"webhookGetMethodDesc": "GET envía los datos como parámetros de consulta y no permite la configuración del cuerpo de la consulta. Útil para disparar monitores PUSH de Uptime Kuma.",
"showOnlyLastHeartbeat": "Mostrar solo el último latido",
"Analytics Type": "Tipo de analíticas",
"Google": "Google",
@@ -1298,7 +1298,7 @@
"checkPriceAt": "Comprueba los precios de {service} en {url}",
"noMonitorsOrStatusPagesSelectedError": "No se puede crear un mantenimiento sin monitores afectados o páginas de estado",
"noMonitorsSelectedWarning": "Estás creando un mantenimiento sin ningún monitor afectado. ¿Estás seguro de que deseas continuar?",
"deleteChildrenMonitors": "Borra también los sub-monitores y sus descendientes si los tuvieran|Borra también todos los {count} sub-monitores directos y sus descendientes si los tuvieran",
"deleteChildrenMonitors": "Tambien elimina los monitores hijos directos y sus descendientes si los tuvieran | Tambien elimina todos los {count} monitores hijos directos y sus descendientes si los tuvieran",
"OptionalParameters": "Parámetros Opcionales",
"aliyun-template-requirements-and-parameters": "La plantilla de SMS de aliyun debe de contener los siguientes parámetros: {parameters}",
"aliyun-template-optional-parameters": "Parámetros opcionales: {parameters}",
@@ -1306,5 +1306,71 @@
"invalidDNSHostname": "Nombre de host inválido. El nombre del host debe ser un FQDN válido. Puede ser un comodín, tener barra-baja o terminar con un punto.",
"wildcardOnlyForDNS": "Los nombres de host comodín sólo son soportados por los monitores DNS.",
"Analytics ID": "ID de Analytics",
"Analytics Script URL": "Script URL de Analytics"
"Analytics Script URL": "Script URL de Analytics",
"enableSSL": "Habilitar SSL/TLS",
"mariadbCaCertificateLabel": "Certificado CA",
"unknownDays": "Días desconocidos",
"No incidents recorded": "No se registraron incidentes",
"Load More": "Cargar más",
"mariadbUseSSLHelptext": "Habilita el uso de una conexión cifrada a tu base de datos. Requerido para la mayoría de las bases de datos en la nube.",
"mariadbCaCertificateHelptext": "Pegue el certificado CA en formato PEM para utilizarlo con certificados autofirmados. Déjelo en blanco si su base de datos utiliza un certificado firmado por una CA pública.",
"versionIs": "Versión: {version}",
"Loading...": "Cargando...",
"days": "{n} día | {n} días",
"hours": "{n} hora | {n} horas",
"minutes": "{n} minuto | {n} minutos",
"minuteShort": "{n} min | {n} min",
"years": "{n} año | {n} años",
"Pin this incident": "Fijar este incidente",
"saveResponseForNotifications": "Guardar respuesta HTTP correcta para notificaciones",
"saveErrorResponseForNotifications": "Guardar respuesta HTTP errónea para notificaciones",
"saveResponseDescription": "Almacena la respuesta HTTP y la pone a disposición de las plantillas de notificación como {templateVariable}",
"responseMaxLength": "Longitud máxima de la respuesta (bytes)",
"Only retry if status code check fails": "Reintentar solo si la comprobación del código de estado falla",
"retryOnlyOnStatusCodeFailureDescription": "Si está habilitado, los reintentos solo se realizarán cuando falle la comprobación del código de estado HTTP (por ejemplo, si el servidor está caído). Si la comprobación del código de estado es correcta pero falla la consulta JSON, el monitor se marcará como inactivo inmediatamente, sin reintentos.",
"responseMaxLengthDescription": "Tamaño máximo de los datos de la respuesta que se van a almacenar. Establezca 0 para ilimitado. Las respuestas más grandes se truncarán. Valor por defecto: 1024 (1KB)",
"logoutCurrentUser": "Cerrar sesión de {username}",
"Incident description": "Descripción del incidente",
"twilioApiKeyHelptext": "La llave de la API es opcional pero recomendada. Puede proporcionar el SID de la cuenta y el Token de Autorizacion desde la consola de Twilio o el SID de la cuenta y la llave de la API junto con el secreto de la llave de la API",
"monitorTypeGameServer": "Servidor de Juego",
"monitorTypeDatabase": "Tipo de Monitor de Base de Datos",
"monitorTypeSpecial": "Especial",
"Recipient Numbers": "Números de destinatarios",
"Incident not found or access denied": "No se encontró incidente o acceso denegado",
"Past Incidents": "Incidentes pasados",
"Incident title": "Título del incidente",
"example": "Ejemplo",
"Result": "Resultado",
"lastUpdatedAt": "Última actualización: {date}",
"Actions": "Acciones",
"selectAllMonitorsAria": "Seleccionar todos los monitores",
"deselectAllMonitorsAria": "Deseleccionar todos los monitores",
"lastUpdatedAtFromNow": "Última actualización: {date} ({fromNow})",
"See Jira Cloud Docs": "Ver la documentación de Jura Cloud",
"Cloud ID": "ID de nube",
"API Token": "Token de API",
"templateAvailableVariables": "Variables disponibles",
"selectMonitorMsg": "Selecciona monitores para realizar acciones",
"Examples:": "Ejemplos: {0}",
"Pinned incidents are shown prominently on the status page": "Incidentes marcados se muestran prominentemente en la pagina de estado",
"Edit Incident": "Editar incidente",
"Please input title": "Por favor ingresa título",
"Resolve": "Resolver",
"Resolved": "Resuelto",
"createdAt": "Creado: {date}",
"deleteIncidentMsg": "Estas seguro que quieres eliminar este incidente?",
"Certificate Chain:": "Cadena de certificado:",
"Please input content": "Por favor ingresa contenido",
"dateCreatedAtFromNow": "Fecha de creación: {date} ({fromNow})",
"360messengerAuthToken": "Clave de la API 360messenger",
"360messengerGroupId": "ID de grupo 360messenger",
"360messengerGroupList": "Grupos de WhatsApp",
"ntfyUseTemplateDescription": "Habilite esta opción para personalizar los títulos y mensajes de notificación mediante plantillas LiquidJS",
"ntfyCustomTitle": "Plantilla de título personalizado",
"ntfyCustomMessage": "Plantilla de mensaje personalizado",
"ntfyNotificationTemplateFallback": "Dejar en blanco para utilizar el formato predeterminado de Uptime Kuma",
"Screenshot Delay": "Retraso de captura de pantalla (espera {milliseconds})",
"milliseconds": "{n} milisegundos | {n} milisegundos",
"snmpV3Username": "Nombre de usuario SNMPv3",
"ntfyUseTemplate": "Personalizar plantillas de notificación"
}

View File

@@ -691,7 +691,7 @@
"uninstalling": "Désinstallation",
"confirmUninstallPlugin": "Voulez-vous vraiment désinstaller ce plugin ?",
"Custom Monitor Type": "Type de sonde personnalisé",
"markdownSupported": "Syntaxe Markdown supportée, Si vous utilisez du HTML, évitez les espaces en début de ligne afin de prévenir les problèmes de mise en forme.",
"markdownSupported": "Syntaxe Markdown supportée. Si vous utilisez du HTML, évitez les espaces en début de ligne afin d'éviter les problèmes de mise en forme.",
"Google Analytics ID": "Identifiant Google Analytics",
"Server Address": "Adresse du serveur",
"Learn More": "En savoir plus",
@@ -1460,7 +1460,7 @@
"lastUpdatedAtFromNow": "Dernière mise à jour : {date} ({fromNow})",
"Suppress Notifications": "Supprimer les notifications",
"discordSuppressNotificationsHelptext": "Lorsqu'elle est activée, les messages seront publiés sur le canal mais ne déclencheront pas de notifications push ou de notifications de bureau pour les destinataires.",
"domain_expiry_unsupported_is_icann": "Le domaine « {domain} » n'est pas éligible à la surveillance de l'expiration des domaines, car son suffixe public « .{publicSuffix} » n'est pas conforme à ICAN",
"domain_expiry_unsupported_is_icann": "Le domaine « {domain} » n'est pas éligible à la surveillance de l'expiration des domaines, car son suffixe public « .{publicSuffix} » n'est pas géré par l'ICANN",
"versionIs": "Version : {version}",
"logoutCurrentUser": "Déconnexion {username}",
"createdAt": "Créé le : {date}",
@@ -1586,5 +1586,39 @@
"RegexMatch": "Saisissez une expression régulière pour faire correspondre la valeur de l'enregistrement",
"domainExpiryNotificationHelp": "Le nombre de jours à l'avance peut être configuré dans les Paramètres.",
"matrixUseTemplate": "Utiliser un modèle de message personnalisé",
"matrixUseTemplateDescription": "Si cette option est activée, le message sera envoyé à l'aide d'un modèle personnalisé."
"matrixUseTemplateDescription": "Si cette option est activée, le message sera envoyé à l'aide d'un modèle personnalisé.",
"signalUseTemplate": "Utiliser un modèle de message personnalisé",
"signalUseTemplateDescription": "Si cette option est activée, le message sera envoyé à l'aide d'un modèle personnalisé. Vous pouvez utiliser les modèles Liquid pour personnaliser le format de la notification.",
"360messengerAuthToken": "Clé API 360messenger",
"360messengerRecipient": "Numéro(s) de téléphone du destinataire",
"360messengerGroupId": "ID du groupe 360messenger",
"360messengerUseTemplate": "Utilisez un modèle de message personnalisé",
"360messengerTemplate": "Modèle de message 360messenger",
"360messengerGroupList": "Groupes WhatsApp",
"360messengerSelectGroupList": "Sélectionnez un groupe à ajouter",
"360messengerEnableSendToGroup": "Activer l'envoi aux groupes WhatsApp",
"360messengerCustomMessageTemplate": "Modèle de message personnalisé",
"360messengerMessageTemplate": "Modèle de message",
"360messengerWayToGetUrlAndToken": "Vous pouvez obtenir votre clé API 360messenger à partir de {0}.",
"360messengerErrorNoApiKey": "Veuillez d'abord saisir votre clé API 360messenger.",
"360messengerErrorNoGroups": "Aucun groupe WhatsApp n'a été trouvé pour ce compte.",
"360messengerErrorApi": "Impossible de charger la liste des groupes WhatsApp (Erreur {statusCode} : {message}).",
"360messengerErrorGeneric": "Impossible de charger la liste des groupes WhatsApp : {message}",
"GlobalpingLocationDescription": "Le champ « Emplacement » accepte les continents, les pays, les régions, les villes, les ASN, les FAI ou les régions cloud. Vous pouvez combiner des filtres avec {plus} (par exemple, {amazonPlusGermany} ou {comcastPlusCalifornia}). Si la latence est un critère important, utilisez des filtres pour restreindre l'emplacement à une petite région afin d'éviter les pics de latence et, pour une meilleure stabilité, activez le filtre {datacenter}. {fullDocs}.",
"GlobalpingMultipleLocationsError": "La prise en charge de plusieurs emplacements n'est pas assurée ; veuillez utiliser un seul emplacement par sonde.",
"360messengerSelectedGroupID": "Identifiant(s) du groupe sélectionné(s)",
"360messengerEnableCustomMessage": "Activez un modèle de message personnalisé au lieu du message par défaut.",
"360messengerWayToWriteRecipient": "Saisissez un ou plusieurs numéros de téléphone au format international sans signe plus (par exemple : {0}). Séparez les numéros par des virgules.",
"monitorTypeSpecial": "Spécial",
"monitorTypeGameServer": "Serveur de jeu",
"monitorTypeDatabase": "Sonde de Type base de données",
"fluxerMessageFormatNormal": "Normal (intégrations riches)",
"fluxerUseMessageTemplate": "Utiliser un modèle de message personnalisé",
"Fluxer Webhook URL": "URL du Webhook de Fluxer",
"wayToGetFluxerURL": "Vous pouvez obtenir cela en allant dans les paramètres du canal cible > Webhooks > Créer un webhook > Copier lURL du webhook.",
"fluxerMessageFormat": "Format du message",
"fluxerMessageFormatCustom": "Modèle personnalisé",
"fluxerMessageTemplate": "Modèle de message",
"fluxerMessageFormatMinimalist": "Minimalist (statut court",
"fluxerUseMessageTemplateDescription": "Si activé, le message sera envoyé en utilisant un modèle personnalisé (LiquidJS). Laissez vide pour utiliser le format Uptime Kuma par défaut."
}

View File

@@ -391,7 +391,7 @@
"Partially Degraded Service": "Seirbhís Díghrádaithe i bPáirt",
"defaultNotificationName": "Mo {notification} Fhógra ({number})",
"webhookFormDataDesc": "Tá {multipart} go maith do PHP. Beidh gá an JSON a pharsáil le {decodeFunction}",
"liquidIntroduction": "Baintear an teimpléad amach tríd an teanga Templating Leachtach. Féach ar an {0} le haghaidh treoracha úsáide. Seo iad na hathróga atá ar fáil:",
"liquidIntroduction": "Baintear amach inúsáidteacht teimpléadaithe tríd an teanga teimpléadaithe Liquid. Féach ar an {0} le haghaidh treoracha úsáide.",
"webhookAdditionalHeadersDesc": "Socraítear ceanntásca breise a sheoltar leis an gcuaille gréasáin. Ba cheart gach ceanntásc a shainiú mar eochair/luach JSON.",
"HeadersInvalidFormat": "Níl na ceanntásca iarratais bailí JSON: ",
"steamApiKeyDescription": "Chun monatóireacht a dhéanamh ar Fhreastalaí Cluiche Gaile is gá duit eochair Steam Web-API. Is féidir leat deochair API a chlárú anseo: ",
@@ -1107,7 +1107,7 @@
"Message Template": "Teimpléad Teachtaireachta",
"Template Format": "Formáid Teimpléid",
"YZJ Robot Token": "Comhartha robot YZJ",
"YZJ Webhook URL": "YZJ Webhook URL",
"YZJ Webhook URL": "URL Crúca Gréasán YZJ",
"Add Tags": "Cuir Clibeanna leis",
"tagAlreadyOnMonitor": "Tá an clib seo (ainm agus luach) ar an monatóir cheana féin nó á cur leis ar feitheamh.",
"tagAlreadyStaged": "Tá an clib seo (ainm agus luach) curtha i láthair cheana féin don bhaisc seo.",
@@ -1377,7 +1377,7 @@
"TLS Alert Spec": "RFC 8446",
"Suppress Notifications": "Fógraí a Chosc",
"discordSuppressNotificationsHelptext": "Nuair a bheidh sé cumasaithe, cuirfear teachtaireachtaí chuig an gcainéal ach ní spreagfar fógraí brú ná fógraí deisce do fhaighteoirí.",
"domain_expiry_unsupported_is_icann": "Ní iarrthóir é an fearann \"{domain}\" le haghaidh monatóireachta ar dhul in éag fearainn, toisc nach bhfuil a iarmhír phoiblí \".{publicSuffix}\" ICAN",
"domain_expiry_unsupported_is_icann": "Ní iarrthóir é an fearann \"{domain}\" le haghaidh monatóireachta ar dhul in éag fearainn, toisc nach bhfuil a iarmhír phoiblí \".{publicSuffix}\" á bhainistiú ag ICANN",
"notificationUniversal": "Uilíoch",
"notificationChatPlatforms": "Ardáin Comhrá",
"notificationPushServices": "Seirbhísí Brúigh",
@@ -1455,5 +1455,115 @@
"deleteMonitorsMsg": "An bhfuil tú cinnte gur mian leat na monatóirí roghnaithe a scriosadh?",
"Sets end time based on start time": "Socraíonn sé am críochnaithe bunaithe ar am tosaithe",
"Please set start time first": "Socraigh an t-am tosaithe ar dtús le do thoil",
"expectedTlsAlertDescription": "Roghnaigh an foláireamh TLS a bhfuil súil agat go dtabharfaidh an freastalaí ar ais é. Úsáid {code} chun a fhíorú go ndiúltaíonn críochphointí mTLS do naisc gan deimhnithe cliaint. Féach {link} le haghaidh sonraí."
"expectedTlsAlertDescription": "Roghnaigh an foláireamh TLS a bhfuil súil agat go dtabharfaidh an freastalaí ar ais é. Úsáid {code} chun a fhíorú go ndiúltaíonn críochphointí mTLS do naisc gan deimhnithe cliaint. Féach {link} le haghaidh sonraí.",
"Teltonika SMS Gateway": "Geata SMS Teltonika",
"teltonikaVersionWarning": "Éilíonn an soláthraí fógraí seo go ritheann do ghléas Teltonika leagan 7.14.0 de RMS, nó níos airde.",
"teltonikaUrl": "URL do ghléis Teltonika",
"teltonikaUrlHelptext": "Ba chóir URL a shonrú mar bhunadh iomlán, m.sh. {0}, nó {1}.",
"teltonikaUnsafeTls": "Déan neamhaird den bhailíochtú teastais",
"teltonikaUsername": "Ainm úsáideora API",
"discordMessageFormatMinimalist": "Íostach (stádas gearr)",
"discordMessageFormatCustom": "Teimpléad saincheaptha",
"discordUseMessageTemplate": "Úsáid teimpléad teachtaireachta saincheaptha",
"RecordMatch": "Meaitseáil luach taifead",
"RegexMatch": "Cuir isteach regex a mheaitseálann luach an taifid",
"account settings": "socruithe cuntais",
"Location": "Suíomh",
"Check for": "Seiceáil le haghaidh",
"templateAvailableVariables": "Athróga atá ar fáil",
"example": "Sampla",
"Result": "Toradh",
"See Jira Cloud Docs": "Féach ar Dhoiciméid Jira Cloud",
"API Token": "Comhartha API",
"Cloud ID": "Aitheantas Néil",
"Click Deploy → New deployment → Web app": "Cliceáil Imscaradh → Imscaradh nua → Aip ghréasáin",
"Go to Extensions → Apps Script": "Téigh go dtí Síneadh → Script na nAipeanna",
"Paste the script code (see below)": "Greamaigh an cód scripte (féach thíos)",
"Google Apps Script Code": "Cód Scripte Google Apps",
"aboutJiraCloudId": "Tuilleadh eolais faoi Jira Cloud ID: {0}",
"see Jira Cloud Docs": "féach ar Dhoiciméid Jira Cloud",
"ntfyUseTemplate": "Saincheap teimpléid fógra",
"certificateExpiryNotificationHelp": "Is féidir líon na laethanta roimh ré a chumrú sna Socruithe.",
"Deploy a Google Apps Script as a web app and paste the URL here": "Imscaradh Script Google Apps mar aip ghréasáin agus greamaigh an URL anseo",
"Quick Setup Guide": "Treoir Socraithe Tapa",
"Copy to Clipboard": "Cóipeáil chuig an nGearrthaisce",
"Copied to clipboard!": "Cóipeáilte chuig an ghearrthaisce!",
"Failed to copy to clipboard": "Theip ar chóipeáil chuig an ghearrthaisce",
"slackIncludeGroupName": "Cuir ainm an ghrúpa monatóireachta san áireamh",
"ntfyCustomTitle": "Teimpléad Teidil Saincheaptha",
"ntfyCustomMessage": "Teimpléad Teachtaireachta Saincheaptha",
"ntfyNotificationTemplateFallback": "Fág bán chun an fhormáid réamhshocraithe Uptime Kuma a úsáid",
"discordUseMessageTemplateDescription": "Má tá sé cumasaithe, seolfar an teachtaireacht ag baint úsáide as teimpléad saincheaptha (LiquidJS). Fág bán é chun an fhormáid réamhshocraithe Uptime Kuma a úsáid.",
"discordMessageTemplate": "Teimpléad Teachtaireachta",
"Globalping - Access global monitoring probes": "Globalping - Rochtain ar bhrabhsálaithe monatóireachta domhanda",
"Globalping API Token": "Comhartha API Globalping",
"globalpingApiTokenDescription": "Faigh do Chomhartha API Globalping ag {0}.",
"GlobalpingHostname": "Sprioc tomhais atá inrochtana go poiblí. De ghnáth ainm óstach nó seoladh IPv4/IPv6, ag brath ar an gcineál tomhais.",
"domainExpiryNotificationHelp": "Is féidir líon na laethanta roimh ré a chumrú sna Socruithe.",
"halopsa_setup_step5": "Cumraigh runbook chun monitor_id a úsáid chun foláirimh a mheaitseáil le ticéid atá ann cheana féin",
"teltonikaUsernameHelptext": "Moladh: Cruthaigh cuntas ar leithligh atá teoranta do theachtaireachtaí SMS amháin agus cuir isteach a ainm úsáideora anseo",
"teltonikaPassword": "Pasfhocal API",
"teltonikaPasswordHelptext": "Is féidir leat pasfhocal úsáideora API a shainiú i do ródaire Teltonika, m.sh. {0}",
"teltonikaModem": "Aitheantas Móideim",
"teltonikaModemHelptext": "Caithfidh aitheantas an mhóideim SMS a bheith san fhormáid {0}. Féach https://developers.teltonika-networks.com/reference/ le haghaidh treorach.",
"Webhook Payload Fields": "Réimsí Ualaigh Webhook",
"halopsa_payload_desc": "Seoltar na réimsí seo a leanas chuig do chrúca gréasáin Halo PSA:",
"halopsa_field_title": "Teideal an fholáirimh (i gcónaí 'Foláireamh Kuma Am Ar Fáil')",
"halopsa_field_status": "Stádas monatóireachta: SUAS, SÍOS, FÓGRA, nó ANAITHNITHE",
"halopsa_field_monitor": "Ainm an mhonatóra",
"halopsa_field_message": "Teachtaireacht foláirimh iomlán le stádas agus sonraí",
"halopsa_field_timestamp": "Stampa ama imeachta i bhformáid ISO 8601",
"teamsEnableTags": "Cuir clibeanna san áireamh",
"teamsEnableTagsDescription": "Má tá sé cumasaithe, beidh na clibeanna monatóireachta san áireamh sa teachtaireacht.",
"Set 'Execute as: Me' and 'Who has access: Anyone'": "Socraigh 'Forghníomhaigh mar: Mise' agus 'Cé a bhfuil rochtain aige: Aon duine'",
"ntfyUseTemplateDescription": "Cumasaigh seo chun teidil agus teachtaireachtaí fógraí a shaincheapadh ag baint úsáide as teimpléadú LiquidJS",
"discordMessageFormat": "Formáid Teachtaireachta",
"discordMessageFormatNormal": "Gnáth (leabaithe saibhre)",
"GlobalpingMonitorDescription": "Cuireann Globalping rochtain ar fáil ar na mílte braiteoir pobail chun tástálacha agus tomhais líonra a reáchtáil. Tá teorainn 250 tástáil san uair socraithe do gach úsáideoir gan ainm. Chun an teorainn a dhúbailt go 500 san uair, sábháil do chomhartha i {accountSettings}. Seiceáil na {docs} le haghaidh tuilleadh eolais.",
"GlobalpingLocation": "Glacann an réimse suímh le hilchríocha, tíortha, réigiúin, cathracha, ASNanna, ISPanna, nó réigiúin scamall. Is féidir leat scagairí a chomhcheangal le {plus} (m.sh. {amazonPlusGermany} nó {comcastPlusCalifornia}). Más méadracht thábhachtach í an mhoill, bain úsáid as scagairí chun an suíomh a chúngú síos go réigiún beag chun borradh a sheachaint. {fullDocs}.",
"Monitor Subtype": "Fochineál Monatóra",
"halopsa_field_monitor_id": "Aitheantóir uathúil monatóireachta (nialasach le haghaidh fógraí tástála) - Úsáid é seo chun foláirimh a mheaitseáil le ticéid",
"halopsa_field_uptime_kuma_version": "Uimhir leagan Kuma Am Ar Fáil",
"halopsa_id_usage_hint": "💡 Leid: Bain úsáid as monitor_id chun foláirimh a mheaitseáil go hiontaofa le ticéid, agus heartbeat_id chun stair imeachtaí a rianú",
"teltonikaUnsafeTlsDescription": "Má mhúchann tú bailíochtú teastais TLS, osclaítear tú suas le hionsaithe ar an gcosán (fear-sa-lár), rud a d'fhéadfadh sceitheanna sonraí agus glacadh seilbhe córas a bheith mar thoradh air. Ná múch bailíochtú teastais mura nglacann tú leis an veicteoir ionsaithe seo. Molaimid LetsEncrypt a úsáid le hathnuachan uathoibríoch.",
"teltonikaPhoneNumber": "Uimhir Ghutháin",
"teltonikaPhoneNumberHelptext": "Ní mór don uimhir a bheith san fhormáid idirnáisiúnta {0}, {1}. Ní cheadaítear ach uimhir amháin.",
"matrixUseTemplate": "Úsáid teimpléad teachtaireachta saincheaptha",
"matrixUseTemplateDescription": "Má tá sé cumasaithe, seolfar an teachtaireacht ag baint úsáide as teimpléad saincheaptha.",
"Open your Google Spreadsheet": "Oscail do Scarbhileog Google",
"Copy the web app URL and paste it above": "Cóipeáil URL an aip ghréasáin agus greamaigh thuas é",
"slackIncludeGroupNameDescription": "Má chuirtear ar chumas é, cuirfear cosán an ghrúpa monatóirí san áireamh sna fógraí chun cabhrú le monatóirí leis an ainm céanna a idirdhealú i ngrúpaí éagsúla.",
"slackUseTemplate": "Úsáid teimpléad teachtaireachta saincheaptha",
"slackUseTemplateDescription": "Má tá sé cumasaithe, seolfar an teachtaireacht ag baint úsáide as teimpléad saincheaptha. Is féidir leat teimpléadú Liquid a úsáid chun faisnéis ghrúpa monatóireachta a áireamh trí monitorJSON.path nó monitorJSON.pathName.",
"Protocol": "Prótacal",
"GlobalpingLocationDocs": "Doiciméadacht iomlán ionchuir suímh",
"GlobalpingIpFamilyInfo": "An leagan IP le húsáid. Ní cheadaítear é seo ach amháin má tá an sprioc ina hainm óstach.",
"GlobalpingResolverInfo": "Seoladh IPv4/IPv6 nó Ainm Fearainn Cáilithe go Lán (FQDN). Is é an réamhshocrú ná réiteoir líonra áitiúil an tóireadóra. Is féidir leat an freastalaí réiteora a athrú am ar bith.",
"Jira Service Management": "Bainistíocht Seirbhíse Jira",
"Google Apps Script Webhook URL": "URL Gréasáin-chrúca Script Google Apps",
"360messengerEnableSendToGroup": "Cumasaigh seoltaí chuig grúpa(í) WhatsApp",
"360messengerAuthToken": "Eochair API 360messenger",
"360messengerRecipient": "Uimhir(í) theileafóin an fhaighteora",
"360messengerGroupId": "Aitheantas Grúpa 360messenger",
"360messengerUseTemplate": "Úsáid teimpléad teachtaireachta saincheaptha",
"360messengerTemplate": "Teimpléad Teachtaireachta 360messenger",
"360messengerGroupList": "Grúpaí WhatsApp",
"360messengerSelectGroupList": "Roghnaigh grúpa le cur leis",
"360messengerSelectedGroupID": "Aitheantais Ghrúpa Roghnaithe",
"360messengerCustomMessageTemplate": "Teimpléad teachtaireachta saincheaptha",
"360messengerEnableCustomMessage": "Cumasaigh teimpléad teachtaireachta saincheaptha in ionad an teachtaireachta réamhshocraithe.",
"360messengerMessageTemplate": "Teimpléad teachtaireachta",
"360messengerWayToGetUrlAndToken": "Is féidir leat d'eochair API 360messenger a fháil ó {0}.",
"360messengerErrorNoApiKey": "Cuir isteach deochair API 360messenger ar dtús.",
"360messengerErrorNoGroups": "Ní bhfuarthas aon ghrúpaí WhatsApp don chuntas seo.",
"360messengerErrorApi": "Ní féidir liosta na ngrúpaí WhatsApp a luchtú (Earráid {statusCode}: {message}).",
"360messengerErrorGeneric": "Ní féidir an liosta grúpa WhatsApp a luchtú: {message}",
"GlobalpingLocationDescription": "Glacann an réimse suímh le hilchríocha, tíortha, réigiúin, cathracha, ASNanna, ISPanna, nó réigiúin scamall. Is féidir leat scagairí a chomhcheangal le {plus} (m.sh. {amazonPlusGermany} nó {comcastPlusCalifornia}). Más méadracht thábhachtach í an mhoill, bain úsáid as scagairí chun an suíomh a chúngú síos go réigiún beag chun spící a sheachaint agus socraigh an scagaire {datacenter} ar mhaithe le cobhsaíocht níos fearr. {fullDocs}.",
"GlobalpingMultipleLocationsError": "Ní thacaítear le hilshuíomhanna, bain úsáid as suíomh amháin do gach monatóir le do thoil.",
"360messengerWayToWriteRecipient": "Cuir isteach uimhir theileafóin amháin nó níos mó i bhformáid idirnáisiúnta gan móide tosaigh (m.sh. {0}). Scar uimhreacha iolracha le camóga.",
"signalUseTemplate": "Úsáid teimpléad teachtaireachta saincheaptha",
"signalUseTemplateDescription": "Má tá sé cumasaithe, seolfar an teachtaireacht ag baint úsáide as teimpléad saincheaptha. Is féidir leat teimpléadú Liquid a úsáid chun formáid an fhógra a shaincheapadh.",
"monitorTypeGameServer": "Freastalaí Cluiche",
"monitorTypeDatabase": "Cineál Monatóra Bunachar Sonraí",
"monitorTypeSpecial": "Speisialta"
}

View File

@@ -265,7 +265,7 @@
"apiCredentials": "Vjerodajnice za API",
"octopushLegacyHint": "Koristite li staru inačicu usluge Octopush (2011-2020) ili noviju inačicu?",
"Check octopush prices": "Provjerite cijene usluge Octopush {0}.",
"octopushPhoneNumber": "Telefonski broj (međunarodni format, primjerice: +38512345678) ",
"octopushPhoneNumber": "Telefonski broj (međunarodni format, primjerice: +38512345678)",
"octopushSMSSender": "Naziv SMS pošiljatelja : 3-11 alfanumeričkih znakova i razmak (a-zA-Z0-9)",
"LunaSea Device ID": "LunaSea identifikator uređaja",
"Apprise URL": "URL usluge Apprise",
@@ -566,7 +566,7 @@
"Examples": "Primjeri",
"Home Assistant URL": "URL Home Assistanta",
"Long-Lived Access Token": "Dugotrajni pristupni token",
"Long-Lived Access Token can be created by clicking on your profile name (bottom left) and scrolling to the bottom then click Create Token. ": "Dugotrajni pristupni token može se kreirati klikom na korisničko ime (dolje lijevo) u Home Assistantu, pomicanjem do dna, te klikom na 'Create Token'. ",
"Long-Lived Access Token can be created by clicking on your profile name (bottom left) and scrolling to the bottom then click Create Token. ": "Dugotrajni pristupni token može se kreirati klikom na korisničko ime (dolje lijevo) u Home Assistantu, pomicanjem do dna, te klikom na 'Create Token'.",
"Notification Service": "Notification Service",
"default: notify all devices": "zadano ponašanje: obavijesti sve uređaje",
"A list of Notification Services can be found in Home Assistant under \"Developer Tools > Services\" search for \"notification\" to find your device/phone name.": "Popis servisa za obavijesti u Home Assistantu nalaze se pod \"Developer Tools > Services\" te pretražiti \"notification\".",
@@ -815,7 +815,7 @@
"Kafka Brokers": "Kafka brokeri",
"Game": "Igra",
"Passive Monitor Type": "Pasivni tip Monitora",
"markdownSupported": "Podržana je Markdown sintaksa",
"markdownSupported": "Podržana je Markdown sintaksa. Ako koristite HTML, izbjegavajte početne razmake kako biste spriječili probleme s formatiranjem.",
"statusMaintenance": "Održavanje",
"General Monitor Type": "Općeniti tip Monitora",
"Maintenance": "Održavanje",
@@ -867,7 +867,7 @@
"pushOthers": "Ostali",
"Reset Token": "Poništi token",
"GrafanaOncallUrl": "URL za Grafana OnCall",
"liquidIntroduction": "Mogućnost korištenja predložaka postignuto je putem jezika Liquid. Pogledajte upute korištenja na {0}. Ovo su dostupne varijable:",
"liquidIntroduction": "Mogućnost korištenja predložaka postignuto je putem jezika Liquid. Pogledajte upute korištenja na {0}.",
"templateLimitedToUpDownCertNotifications": "dostupno samo za obavijesti dostupnosti te isteka certifikata",
"emailCustomisableContent": "Prilagodljiv sadržaj",
"smtpLiquidIntroduction": "Sljedeća dva polja mogu se izraditi putem Liquid jezika za predloške. Upute za korištenje dostupne su na {0}. Ovo su dostupne varijable:",
@@ -1237,5 +1237,314 @@
"twilloMessagingServiceSIDHelptext": "Ovdje unesite SID svoje usluge za slanje poruka ako koristite {twillo_messaging_service_help_link} za upravljanje pošiljateljima i ostalim značajkama",
"enableSSL": "Omogući SSL/TLS",
"mariadbUseSSLHelptext": "Omogućiti za korištenje šifrirane veze do bate. Postavka je nužna za većinu baza u oblaku.",
"mariadbCaCertificateLabel": "CA certifikat"
"mariadbCaCertificateLabel": "CA certifikat",
"teltonikaUsername": "Korisničko ime za API",
"teltonikaUsernameHelptext": "Preporuka: izradite zaseban račun koji je ograničen samo na slanje SMS poruka i ovdje unesite njegovo korisničko ime",
"Teltonika SMS Gateway": "Teltonika SMS pristupnik",
"teltonikaVersionWarning": "Ovaj pružatelj obavijesti zahtijeva da vaš Teltonika uređaj koristi RMS verziju 7.14.0 ili noviju.",
"teltonikaUrl": "URL vašeg Teltonika uređaja",
"teltonikaUrlHelptext": "URL treba biti naveden kao puni origin, npr. {0} ili {1}.",
"teltonikaUnsafeTlsDescription": "Isključivanjem provjere valjanosti TLS certifikata otvarate se za man-in-the-middle napade, što potencijalno može dovesti do curenja podataka i ostalih napada. Ne isključujte provjeru valjanosti certifikata osim ako prihvaćate ovaj rizik.",
"teltonikaPassword": "Lozinka za API",
"teltonikaUnsafeTls": "Zanemari provjeru valjanosti certifikata",
"Show this Maintenance Message on which Status Pages": "Na kojim statusnim stranicama prikazati ovu poruku o održavanju",
"Expand All Groups": "Proširi sve grupe",
"discordMessageFormat": "Format poruke",
"GlobalpingLocationDocs": "Dokumentacija za unos lokacije",
"labelDomainNameExpiryNotification": "Obavijest o isteku domene",
"minimumIntervalWarning": "Intervali kraći od 20 sekundi mogu dovesti do loših performansi.",
"Halo PSA": "Halo PSA",
"Halo PSA Webhook URL": "URL webhooka za Halo PSA",
"username": "Korisničko ime",
"halopsa_setup_step3": "Kopirajte URL webhooka i zalijepite ga u polje iznad",
"Clear current filters": "Obriši trenutne filtere",
"Sort by status": "Sortiraj po statusu",
"Sort by name": "Sortiraj po imenu",
"Sort by uptime": "Sortiraj po vremenu rada",
"Severity": "Ozbiljnost",
"SSL/TLS": "SSL/TLS",
"slackIncludeGroupName": "Uključi naziv grupe monitora",
"unknownDays": "Nepoznat broj dana",
"No incidents recorded": "Nema zabilježenih incidenata",
"Load More": "Učitaj više",
"Loading...": "Učitavanje...",
"Monitors": "{n} Monitor | {n} Monitora",
"days": "{n} dan | {n} dana",
"minutes": "{n} minuta | {n} minuta",
"minuteShort": "{n} min | {n} min",
"years": "{n} godina | {n} godina",
"Pin this incident": "Prikvači ovaj incident",
"ignoreSecWebsocketAcceptHeaderDescription": "Omogućuje poslužitelju da ne odgovori sa zaglavljem Sec-WebSocket-Accept ako nadogradnja websocketa uspije.",
"Ignore Sec-WebSocket-Accept header": "Ignoriraj {0} zaglavlje",
"wsSubprotocolDescription": "Unesite popis podprotokola odvojen zarezima. Za više informacija o podprotokolima pogledajte {documentation}",
"wsCodeDescription": "Za više informacija o statusnim kodovima, pogledajte {rfc6455}",
"Subprotocol(s)": "Podprotokol(i)",
"saveResponseForNotifications": "Spremi uspješni HTTP odgovor za obavijesti",
"saveErrorResponseForNotifications": "Spremi HTTP odgovor s greškom za obavijesti",
"responseMaxLength": "Maksimalna duljina odgovora (u oktetima)",
"responseMaxLengthDescription": "Maksimalna veličina podataka odgovora za pohranu. Postavite na 0 za neograničenu veličinu. Veći odgovori bit će skraćeni. Zadano: 1024 (1 KiB)",
"Incident not found or access denied": "Incident nije pronađen ili je pristup odbijen",
"Incident title": "Naslov incidenta",
"Resolver Server(s)": "DNS poslužitelj",
"Edit Incident": "Uredi incident",
"example": "Primjer",
"Result": "Rezultat",
"BodyInvalidFormatBecause": "Tijelo zahtjeva nije valjani JSON. Opis pogreške: {error}",
"invalidDNSHostname": "Nevažeći naziv hosta. Naziv hosta mora biti valjani FQDN. Može sadržavati zamjenski znak (engl. wildcard), imati podcrtu ili završavati točkom.",
"wildcardOnlyForDNS": "Nazivi hostova sa zamjenskim znakom (engl. wildcard) podržani su samo za DNS monitore.",
"invalidURL": "Nevažeći URL",
"Resolve": "Razriješi",
"Resolved": "Razriješeno",
"createdAt": "Stvoreno: {date}",
"lastUpdatedAt": "Zadnje ažurirano: {date}",
"lastUpdatedAtFromNow": "Zadnje ažurirano: {date} ({fromNow})",
"Actions": "Akcije",
"selectedMonitorCountMsg": "odabrano: {n} | odabrano: {n}",
"selectMonitorMsg": "Odaberite Monitore za izvođenje akcija",
"deselectAllMonitorsAria": "Poništi odabir svih Monitora",
"deleteIncidentMsg": "Jeste li sigurni da želite izbrisati ovaj incident?",
"slug is not found": "Slug nije pronađen",
"Please input title": "Unesite naslov",
"dateCreatedAtFromNow": "Stvoreno: {date} ({fromNow})",
"RSS Title": "Naslov RSS-a",
"Cloud ID": "Cloud ID",
"frontendVersionIs": "Frontend inačica: {version}",
"Examples:": "Primjeri: {0}",
"Duration (Minutes)": "Trajanje (u minutama)",
"cronScheduleDescription": "Raspored: {description}",
"notificationChatPlatforms": "Platforme za chat",
"notificationPushServices": "Push usluge",
"notificationSmsServices": "SMS usluge",
"notificationEmail": "E-pošta",
"Please set start time first": "Prvo postavite vrijeme početka",
"Quick Setup Guide": "Vodič za brzo postavljanje",
"Open your Google Spreadsheet": "Otvorite svoju Google proračunsku tablicu",
"checkPriceAt": "Provjerite cijene {service} na {url}",
"OptionalParameters": "Neobavezni parametri",
"Screenshot Delay": "Odgoda snimke zaslona (čekanje {milliseconds})",
"snmpV3Username": "SNMPv3 korisničko ime",
"Select All": "Odaberi sve",
"Deselect All": "Poništi odabir svega",
"mtls-auth-server-cert-label": "Certifikat",
"TLS Alert Spec": "RFC 8446",
"mariadbCaCertificateHelptext": "Zalijepite CA certifikat u PEM formatu za korištenje sa samopotpisanim certifikatima. Ostavite prazno ako vaša baza podataka koristi certifikat potpisan od strane javnog CA.",
"GlobalpingResolverInfo": "IPv4/IPv6 adresa ili potpuno kvalificirani naziv domene (FQDN). Zadano je lokalni DNS posljužitelj sonde. DNS poslužitelja možete promijeniti u bilo kojem trenutku.",
"hours": "{n} sat | {n} sati",
"RecordMatch": "Podudaranje vrijednosti zapisa",
"Only retry if status code check fails": "Ponovno pokušati samo ako provjera statusnog koda ne uspije",
"imageResetConfirmation": "Slika vraćena na zadane vrijednosti",
"retryOnlyOnStatusCodeFailureDescription": "Ako je omogućeno, ponovni pokušaji će se dogoditi samo kada provjera HTTP statusnog koda ne uspije (npr. poslužitelj je u kvaru). Ako provjera statusnog koda prođe, ali JSON upit ne uspije, Monitor će odmah biti označen kao nedostupan bez ponovnih pokušaja.",
"halopsa_setup_step4": "Odaberite Basic autentifikaciju i kreirajte korisničko ime i lozinku. Zatim upišite ili zalijepite korisničko ime i lozinku u polja iznad",
"saveResponseDescription": "Pohranjuje HTTP odgovor i čini ga dostupnim preko predložaka unutar obavijesti kao {templateVariable}",
"logoutCurrentUser": "Odjava {username}",
"Incident description": "Opis incidenta",
"Past Incidents": "Prošli incidenti",
"Pinned incidents are shown prominently on the status page": "Prikvačeni incidenti istaknuti su na statusnoj stranici",
"templateAvailableVariables": "Dostupne varijable",
"HeadersInvalidFormatBecause": "Zaglavlja zahtjeva nisu valjani JSON. Opis pogreške: {error}",
"steamApiKeyDescriptionAt": "Za monitoriranje Steam poslužitelja za igre potreban vam je Steam Web-API ključ. Svoj API ključ možete registrirati na {url}",
"hostnameCannotBeIP": "DNS naziv hosta ne može biti IP adresa. Jeste li htjeli koristiti polje DNS poslužitelja?",
"invalidHostnameOrIP": "Nevažeći naziv hosta ili IP adresa. Naziv hosta mora biti valjani FQDN. Ne može se koristiti zamjenski znak. Može sadržavati podcrtu ili završavati točkom.",
"selectAllMonitorsAria": "Odaberi sve Monitore",
"Certificate Chain:": "Lanac certifikata:",
"Please input content": "Unesite sadržaj",
"Leave blank to use status page title": "Ostaviti prazno za korištenje naslova statusne stranice",
"disableSTARTTLSDescription": "Odaberite ovu opciju za SMTP poslužitelje koji ne podržavaju STARTTLS. Ovo će slati e-poruke putem nešifrirane veze.",
"Sets end time based on start time": "Postavlja vrijeme završetka na temelju vremena početka",
"noMonitorsSelectedWarning": "Izrađujete održavanje bez ikakvih zahvaćenih monitora. Jeste li sigurni da želite nastaviti?",
"Google Apps Script Webhook URL": "URL webhooka za Google Apps Script",
"Deploy a Google Apps Script as a web app and paste the URL here": "Implementirajte Google Apps Script kao web aplikaciju i zalijepite URL ovdje",
"Copy the web app URL and paste it above": "Kopirajte URL web aplikacije i zalijepite ga iznad",
"You can divide numbers with commas or semicolons": "Brojeve možete odvojiti s {comma} ili {semicolon}",
"aliyun-template-requirements-and-parameters": "Predložak za Aliyun SMS mora sadržavati parametre: {parameters}",
"WeCom Mentioned Mobile List": "WeCom popis mobilnih uređaja za spominjanje",
"WeCom Mentioned Mobile List Description": "Unesite telefonske brojeve koje želite spomenuti. Odvojite više brojeva zarezima. Koristite {'@'}all da biste spomenuli sve.",
"Analytics Type": "Tip analitika",
"ntfyCallHelptext": "Broj se poziva kada se oglasi upozorenje. Postavite na 'da' kako biste koristili svoj prvi potvrđeni broj ili unesite određeni telefonski broj (npr. +12223334444). Zahtijeva ntfy Pro i potvrđeni telefonski broj.",
"API Token": "Token za API",
"See Jira Cloud Docs": "Pogledajte Jira Cloud dokumentaciju",
"certificateExpiryNotificationHelp": "Broj dana može se konfigurirati u postavkama.",
"certHostnameMismatch": "Naziv hosta u certifikatu ne odgovara URL-u Monitora.",
"sipsakPingWarning": "Za korištenje Monitora SIP opcija, Uptime Kuma mora biti instalirana bez Dockera sa Sipsak klijentom na istom poslužitelju.",
"notificationIncidentManagement": "Upravljanje incidentima",
"notificationHomeAutomation": "Automatizacija doma",
"notificationOther": "Ostale integracije",
"SMTP Security": "SMTP sigurnost",
"Ignore STARTTLS": "Ignoriraj STARTTLS",
"Use STARTTLS": "Korsti STARTTLS",
"deleteMonitorsMsg": "Jeste li sigurni da želite izbrisati odabrane Monitore?",
"deletedMonitorsMsg": "Obrisan {n} monitor | Obrisano {n} monitora",
"noMonitorsPausedMsg": "Nijedan Monitor nije pauziran (nijedan nije bio aktivan)",
"noMonitorsResumedMsg": "Nijedan Monitor nije pokrenut (svi su već aktivni)",
"bulkDeleteErrorMsg": "Brisanje {n} monitora nije uspjelo | Brisanje {n} monitora nije uspjelo",
"noMonitorsOrStatusPagesSelectedError": "Nije moguće kreirati održavanje bez zahvaćenih monitora ili stranica sa statusom",
"Paste the script code (see below)": "Zalijepite kod skripte (vidi dolje)",
"Go to Extensions → Apps Script": "Idite na Extensions → Apps Script",
"Click Deploy → New deployment → Web app": "Kliknite na Deploy → New deployment → Web app",
"Google Apps Script Code": "Kôd skripte za Google Apps",
"Copy to Clipboard": "Kopiraj u međuspremnik",
"Copied to clipboard!": "Kopirano u međuspremnik!",
"Failed to copy to clipboard": "Kopiranje u međuspremnik nije uspjelo",
"Webhook Payload Fields": "Polja za sadržaj webhooka",
"halopsa_payload_desc": "Sljedeća polja šalju se vašem Halo PSA webhooku:",
"halopsa_field_monitor": "Naziv Monitora",
"aliyun_enable_optional_variables_at_the_risk_of_non_delivery": "Zbog ograničenja operatera, opcionalne varijable omogućujete prihvaćajući rizik neisporuke",
"aliyun-template-optional-parameters": "Neobavezni parametri: {parameters}",
"halopsa_field_title": "Naslov upozorenja (uvijek 'Uptime Kuma Alert')",
"slackUseTemplate": "Koristi prilagođeni predložak poruke",
"aboutJiraCloudId": "Više informacija o Jira Cloud ID-u: {0}",
"see Jira Cloud Docs": "pogledati Jira Cloud dokumentaciju",
"slackIncludeGroupNameDescription": "Ako je omogućeno, grupa Monitora bit će uključena u obavijesti. Tako se mogu razlikovati Monitori s istim nazivom, ali različitim grupama.",
"slackUseTemplateDescription": "Ako je omogućeno, poruka će biti poslana s prilagođenim predloškom. Možete koristiti Liquid predloške za dodavanje informacija o grupi monitora koristeći monitorJSON.path ili monitorJSON.pathName.",
"serwersmsRecipientType": "Tip primatelja",
"serwersmsRecipientTypePhone": "Telefonski broj",
"Analytics Script URL": "URL skripte za analitiku",
"screenshotDelayDescription": "Po želji čeka ovoliko milisekundi prije snimanja zaslona. Maksimalno: {maxValueMs} ms (0,5 × interval).",
"resendLeaveBlankForDefaultSubject": "ostaviti prazno za zadani predmet",
"resendToEmail": "Adresa e-pošte primatelja",
"resendSubject": "Predmet",
"halopsa_setup_step1": "Izradite Integration Runbook u HaloPSA-u (Configuration → Integrations → Integration Runbooks)",
"Sort options": "Opcije sortiranja",
"End": "Kraj",
"teltonikaModem": "Identifikator modema",
"teltonikaPhoneNumber": "Telefonski broj",
"halopsa_setup_step5": "Konfigurirajte runbook za korištenje monitor_id-a za usklađivanje upozorenja s postojećim ticketima",
"teltonikaModemHelptext": "Identifikator SMS modema mora biti u formatu {0}. Za upute pogledajte https://developers.teltonika-networks.com/reference/.",
"teamsEnableTags": "Uključi oznake",
"teamsEnableTagsDescription": "Ako je omogućeno, poruka će sadržavati oznake Monitora.",
"pausedMonitorsMsg": "Pauziran {n} monitor | Pauzirano {n} monitora",
"resumedMonitorsMsg": "Pokrenut {n} monitor | Pokrenuto {n} monitora",
"Set 'Execute as: Me' and 'Who has access: Anyone'": "Postavite 'Execute as: Me' te 'Who has access: Anyone'",
"notificationUniversal": "Univerzalna",
"versionIs": "Inačica: {version}",
"mtls-auth-server-ca-label": "CA",
"smscTranslit": "SMSC Translit",
"matrixUseTemplateDescription": "Ako je omogućeno, poruka će biti poslana pomoću prilagođenog predloška.",
"matrixUseTemplate": "Koristi prilagođeni predložak poruke",
"serwersmsRecipientTypeGroup": "Grupa",
"serwersmsGroupId": "Identifikator grupe",
"serwersmsGroupIdHelptext": "Identifikatori korisnika ili grupa u Kontrolnoj ploči. Ove identifikatore možete preuzeti pomoću grupa akcija / indeksa ili kopiranjem iz grupe za uređivanje u Kontrolnoj ploči.",
"signalUseTemplate": "Koristi prilagođeni predložak poruke",
"signalUseTemplateDescription": "Ako je omogućeno, poruka će biti poslana s prilagođenim predloškom. Možete koristiti Liquid predloške za prilagodbu poruke.",
"octopushEndpoint": "octopush (krajnja točka: {url})",
"legacyOctopushEndpoint": "Zastarijeli Octopush-DM (krajnja točka: {url})",
"Analytics ID": "Identifikator analitika",
"ntfyCall": "Telefonski poziv",
"ntfyUseTemplate": "Prilagodite predloške obavijesti",
"ntfyUseTemplateDescription": "Omogućite ovo za prilagodbu naslova i poruka obavijesti pomoću LiquidJS predložaka",
"ntfyCustomTitle": "Predložak naslova",
"ntfyCustomMessage": "Predložak poruke",
"ntfyNotificationTemplateFallback": "Ostaviti prazno za korištenje zadanog formata Uptime Kume",
"Badge Link Generator": "Generator poveznica za značke korisnika {0}",
"Open Badge Link Generator": "Otvoriti generator poveznica za značke",
"Badge Link Generator Helptext": "Poveznice za značke dostupne su svim Monitorima dodijeljenim javnim statusnim stranicama. Za više informacija pogledajte {documentation}.",
"showOnlyLastHeartbeat": "Prikaži samo posljednjiu provjeru",
"milliseconds": "{n} milisekunda | {n} milisekundi",
"screenshotDelayWarning": "Veće vrijednosti drže preglednik otvorenim dulje, što može povećati potrošnju memorije ako postoji mnogo istovremenih monitora.",
"systemService": "Sistemski servis",
"systemServiceName": "Naziv servisa",
"systemServiceDescription": "Provjerava je li sistemski servis {service_name} aktivan",
"systemServiceDescriptionLinux": "Provjerava je li Linux systemd servis {service_name} aktivan",
"systemServiceDescriptionWindows": "Provjerava je li pokrenut Windows servis {service_name}",
"systemServiceCommandHint": "Korištena naredba: {command}",
"systemServiceExpectedOutput": "Očekivani izlaz: \"{0}\"",
"message": "poruka",
"json_value": "JSON vrijednost",
"Enter the list of nodes": "Unesite popis upravljačkih čvorova RabbitMQ-a",
"Press Enter to add node": "Pritisnuti Enter za dodavanje čvora",
"resendApiKey": "Ponovno pošalji API ključ",
"resendApiHelp": "Ovdje stvorite API ključ: {0}",
"resendFromName": "Naziv pošiljatelja",
"resendFromEmail": "Adresa e-pošte pošiljatelja",
"resendLeaveBlankForDefaultName": "ostaviti prazno za zadano ime",
"discordUseMessageTemplate": "Koristi prilagođeni predložak poruke",
"Google": "Google",
"Plausible": "Plausible",
"Matomo": "Matomo",
"Umami": "Umami",
"Suppress Notifications": "Utišaj obavijesti",
"discordSuppressNotificationsHelptext": "Kada je omogućeno, poruke će biti objavljene na kanalu, ali neće pokretati push ili desktop obavijesti za primatelje.",
"discordMessageFormatNormal": "Normalni (bogati ugrađeni sadržaji)",
"discordMessageFormatMinimalist": "Minimalistički (kratki status)",
"discordMessageFormatCustom": "Prilagođeni predložak",
"discordUseMessageTemplateDescription": "Ako je omogućeno, poruka će biti poslana s prilagođenim predloškom (LiquidJS). Ostavite prazno za korištenje zadanog Uptime Kuma formata.",
"discordMessageTemplate": "Predložak poruke",
"Globalping - Access global monitoring probes": "Globalping - Pristup globalnim sondama za praćenje",
"GlobalpingMonitorDescription": "Globalping omogućuje pristup tisućama sondi za pokretanje mrežnih testova i mjerenja koje održava zajednica. Za sve anonimne korisnike postavljeno je ograničenje od 250 testova na sat. Da biste udvostručili to ograničenje, spremite svoj token u {accountSettings}. Za više informacija pogledajte {docs}.",
"Globalping API Token": "Token za Globalping API",
"globalpingApiTokenDescription": "Preuzmite svoj token za Globalping API na {0}.",
"GlobalpingHostname": "Javno dostupna ciljna točka mjerenja. Obično naziv hosta ili IPv4/IPv6 adresa, ovisno o vrsti mjerenja.",
"GlobalpingLocation": "Polje lokacije prihvaća kontinente, zemlje, regije, gradove, ASN-ove, ISP-ove ili regije u oblaku. Možete kombinirati filtre s {plus} (npr. {amazonPlusGermany} ili {comcastPlusCalifornia}). Ako je latencija važna metrika, upotrijebite filtre za sužavanje lokacije na malu regiju kako biste izbjegli nagli rast prometa. {fullDocs}.",
"GlobalpingIpFamilyInfo": "IP inačica koja će se koristiti. Dopušten unos samo ako je naziv hosta krajnji cilj.",
"RegexMatch": "Unesite regularni izraz koji odgovara vrijednosti zapisa",
"Protocol": "Protokol",
"account settings": "postavke računa",
"Location": "Lokacija",
"Monitor Subtype": "Podtip Monitora",
"Check for": "Provjeriti za",
"Notifications Enabled": "Obavijesti omogućene",
"Allow Notifications": "Dozvoli obavijesti",
"Browser not supported": "Preglednik nije podržan",
"Unable to get permission to notify": "Nije moguće dobiti dopuštenje za obavijesti (zahtjev je odbijen ili ignoriran).",
"Webpush Helptext": "Web push radi samo s SSL (HTTPS) vezama. Za iOS uređaje, web stranicu je potrebno prethodno dodati na početni zaslon.",
"settingsDomainExpiry": "Istek domene",
"labelDomainExpiry": "Istek domene",
"domainExpiryNotificationHelp": "Broj dana može se konfigurirati u postavkama.",
"domainExpiryDescription": "Pošalji obavijest kada domena ističe za:",
"domain_expiry_unsupported_monitor_type": "Praćenje isteka domene nije podržano za ovu vrstu Monitora",
"domain_expiry_unsupported_missing_target": "Za ovaj Monitor nije konfigurirana valjana domena ili naziv hosta",
"domain_expiry_public_suffix_too_short": "\".{publicSuffix}\" je prekratak za vršnu domenu",
"domain_expiry_unsupported_is_icann": "Domena \"{domain}\" nije kandidat za praćenje isteka jer njen javni sufiks \".{publicSuffix}\" nije ICAN",
"domain_expiry_unsupported_is_ip": "\"{hostname}\" je IP adresa. Za praćenje isteka domene potreban je naziv domene",
"domain_expiry_unsupported_unsupported_tld_no_rdap_endpoint": "Praćenje isteka domene nije dostupno za \".{publicSuffix}\" jer IANA ne navodi nijednu RDAP uslugu",
"lowIntervalWarning": "Jeste li sigurni da želite postaviti vrijednost intervala ispod 20 sekundi? Performanse se mogu smanjiti, posebice ako postoji veliki broj Monitora.",
"halopsa_webhook_url_desc": "Unesite URL webhooka iz svog Halo PSA Integration Runbooka (Configuration > Integrations > Custom Integrations > Integration Runbooks). Prilikom izrade webhooka odaberite 'Can only be started from Halo and from a public endpoint'.",
"password": "Lozinka",
"halopsa_username_desc": "Korisničko ime za autentifikaciju s Halo PSA webhookom",
"halopsa_password_desc": "Lozinka za autentifikaciju s Halo PSA webhookom",
"screenshot of the website": "Snimka zaslona web-mjesta",
"mtls-auth-server-cert-placeholder": "Tijelo certifikata",
"mtls-auth-server-key-label": "Ključ",
"mtls-auth-server-key-placeholder": "Tijelo ključa",
"mtls-auth-server-ca-placeholder": "CA poslužitelja",
"avgPing": "Prosj. ping",
"Basic checkbox toggle button group": "Osnovna grupa za checkbox gumbe",
"maxPing": "Maks. ping",
"minPing": "Min. ping",
"Setup Instructions": "Upute za postavljanje",
"halopsa_setup_step2": "Konfigurirajte radnje runbooka za obradu upozorenja (npr. izrada ticketa)",
"Sort by certificate expiry": "Sortiraj po isteku certifikata",
"Splunk Rest URL": "Splunk REST URL",
"Message Format": "Format poruke",
"Region": "Regija",
"PushDeer Server URL": "URL PushDeer poslužitelja",
"To Number": "Broj primatelja",
"GrafanaOncallURL": "URL za Grafana on-call",
"Never": "Nikad",
"System Service": "Sistemski servis",
"playground": "igralište",
"Check Type": "Tip provjere",
"Service Name": "Naziv servisa",
"GRPC Options": "Opcije za GRPC",
"Metadata": "Metapodaci",
"Endpoint": "Krajnja točka",
"Details": "Detalji",
"passwordTooWeak": "Lozinka je preslaba. Trebala bi sadržavati slova i brojke. Mora biti dugačka najmanje 6 znakova.",
"TLS Alerts": "TLS upozorenja",
"expectedTlsAlertDescription": "Odaberite TLS grešku koje očekujete da će poslužitelj vratiti. Upotrijebite {code} za provjeru odbijaju li mTLS krajnje točke veze bez klijentskih certifikata. Za detalje pogledajte {link}.",
"Expected TLS Alert": "Očekivana TLS greška",
"None (Successful Connection)": "Nikakva (uspješna veza)",
"mariadbSocketPathDetectedHelptext": "Povezivanje s bazom podataka kako je navedeno varijablom okruženja {0}.",
"Collapse All Groups": "Sažmi sve grupe",
"halopsa_field_status": "Status Monitora: UP, DOWN, NOTIFICATION, ili UNKNOWN",
"halopsa_field_monitor_id": "Jedinstveni identifikator Monitora (null za testne obavijesti)",
"halopsa_field_message": "Cjelovita poruka upozorenja sa statusom i detaljima",
"halopsa_field_timestamp": "Vremenska oznaka događaja u ISO 8601 formatu",
"halopsa_field_uptime_kuma_version": "Inačica Uptime Kume",
"halopsa_id_usage_hint": "💡 Savjet: Koristite \"monitor_id\" za povezivanje upozorenja s ticketima i \"heartbeat_id\" za praćenje povijesti događaja",
"teltonikaPasswordHelptext": "Možete definirati lozinku korisnika API-ja u svom Teltonika routeru, npr. {0}",
"teltonikaPhoneNumberHelptext": "Broj mora biti u međunarodnom formatu {0}, {1}. Dopušten je samo jedan broj.",
"Jira Service Management": "Jira Service Management",
"Disable STARTTLS": "Onemogući STARTTLS",
"Basic radio toggle button group": "Osnovna grupa za radio gumbe"
}

View File

@@ -1275,5 +1275,8 @@
"Ignore STARTTLS": "Ignora STARTTLS",
"Use STARTTLS": "Utilizza STARTTLS",
"Enter the list of nodes": "Inserisci l'elenco dei nodi di gestione RabbitMQ",
"Press Enter to add node": "Premi Invio per aggiungere il nodo"
"Press Enter to add node": "Premi Invio per aggiungere il nodo",
"enableSSL": "Abilita SSL/TLS",
"mariadbUseSSLHelptext": "Abilita per usare una connessione criptata per il tuo database. Richiesto dalla maggior parte dei database cloud.",
"mariadbCaCertificateLabel": "Certificato CA"
}

View File

@@ -3,5 +3,72 @@
"setupDatabaseChooseDatabase": "Kuru datubāzi izmantosiet?",
"setupDatabaseEmbeddedMariaDB": "Jums nav nekas jādara. Docker imidžā ir iebūvēta un automātiski konfigurēta MariaDB datubāze. Uptime Kuma pieslēgsies šai datubāzei izmantojot unix soketu.",
"setupDatabaseSQLite": "Vienkāršs datu bāzes fails, iesakāms maza izmēra risinājumiem. Pirms versijas v2.0.0 SQLite bija noklusējuma datubāze.",
"setupDatabaseMariaDB": "Pieslēgties ārējai MariaDB datubāzei. Jums būs jākonfigurē datubāzes pieslēgšanās informācija."
"setupDatabaseMariaDB": "Pieslēgties ārējai MariaDB datubāzei. Jums būs jākonfigurē datubāzes pieslēgšanās informācija.",
"Name": "Nosaukums",
"Ping": "Ping",
"Dashboard": "Panelis",
"dbName": "Datubāzes nosaukums",
"enableSSL": "Iespējot SSL/TLS",
"Settings": "Iestatījumi",
"Help": "Palīdzība",
"New Update": "Jauns atjauninājums",
"Language": "Valoda",
"Appearance": "Izskats",
"Theme": "Tēma",
"General": "Vispārīgi",
"Game": "Spēle",
"mariadbCaCertificateLabel": "CA sertifikāts",
"Primary Base URL": "Galvenais bāzes URL",
"Check Update On GitHub": "Pārbaudīt atjauninājumu GitHub",
"List": "Saraksts",
"Home": "Sākums",
"Add": "Pievienot",
"Add New Monitor": "Pievienot jaunu monitoru",
"Quick Stats": "Ātrā statistika",
"Down": "Nedarbojas",
"Pending": "Rindā",
"statusMaintenance": "Tehniskā apkope",
"Maintenance": "Tehniskā apkope",
"Unknown": "Nezināms",
"unknownDays": "Nezināms dienu skaits",
"Cannot connect to the socket server": "Nevar izveidot savienojumu ar soketa serveri",
"Reconnecting...": "Savienojas...",
"General Monitor Type": "Vispārīgais monitora veids",
"pauseDashboardHome": "Pauze",
"Pause": "Pauze",
"DateTime": "Datums un laiks",
"Specific Monitor Type": "Specifiskais monitora veids",
"settingUpDatabaseMSG": "Tiek uzstādīta datubāze. Uzgaidiet, lūdzu, tas var prasīt laiku.",
"mariadbUseSSLHelptext": "Iespējot šifrētu savienojumu ar jūsu datubāzi. Nepieciešams lielākajai daļai mākoņdatubāžu.",
"mariadbCaCertificateHelptext": "Ielīmējiet CA sertifikātu PEM formātā, lai to izmantotu ar pašparakstītiem sertifikātiem. Atstājiet tukšu, ja jūsu datubāze izmanto publiskas sertifikācijas iestādes parakstītu sertifikātu.",
"Passive Monitor Type": "Pasīvais monitora veids",
"markdownSupported": "Tiek atbalstīta Markdown sintakse. Ja izmantojat HTML, izvairieties no atstarpēm rindas sākumā, lai novērstu formatēšanas problēmas.",
"versionIs": "Versija: {version}",
"monitorTypeGameServer": "Spēļu serveris",
"monitorTypeDatabase": "Datubāzes monitora veids",
"monitorTypeSpecial": "Specifisks",
"Message": "Ziņa",
"No incidents recorded": "Nav reģistrētu incidentu",
"Load More": "Ielādēt vairāk",
"Loading...": "Ielāde...",
"No important events": "Nav svarīgu notikumu",
"Resume": "Turpināt",
"Edit": "Labot",
"Delete": "Dzēst",
"Current": "Pašreizējais",
"Uptime": "Darbības laiks",
"Cert Exp.": "Sert. term.",
"Monitors": "{n} monitors | {n} monitori",
"now": "tagad",
"time ago": "pirms {0}",
"days": "{n} diena | {n} dienas",
"hours": "{n} stunda | {n} stundas",
"minutes": "{n} minūte | {n} minūtes",
"minuteShort": "{n} minūte | {n} minūtes",
"years": "{n} gads| {n} gadi",
"Response": "Atbilde",
"Pin this incident": "Piespraust šo incidentu",
"Monitor Type": "Monitora tips",
"Up": "Darbojas",
"Status": "Status"
}

View File

@@ -994,7 +994,7 @@
"and": "en",
"snmpCommunityStringHelptext": "Deze string fungeert als een wachtwoord om toegang tot SNMP-apparaten te verifiëren en te beheren. Match het met de configuratie van uw SNMP-apparaat.",
"groupOnesenderDesc": "Zorg ervoor dat de GroupID juist is. Om een bericht naar een groep te sturen, bijvoorbeeld: 628123456789-342345",
"privateOnesenderDesc": "Zorg ervoor dat het telefoonnummer juist is. Om een bericht te sturen naar een privenummer, bijvoorbeeld: 628123456789",
"privateOnesenderDesc": "Zorg ervoor dat het telefoonnummer juist is. Om een bericht te sturen naar een privénummer, bijvoorbeeld: 628123456789",
"now": "nu",
"time ago": "{0} geleden",
"-year": "-jaar",
@@ -1007,7 +1007,7 @@
"Host Onesender": "Host Onesender",
"Token Onesender": "Token Onesender",
"Recipient Type": "Ontvanger Type",
"Private Number": "Privenummer",
"Private Number": "Privénummer",
"Group ID": "Groep ID",
"wayToGetOnesenderUrlandToken": "U kunt de URL en Token krijgen door naar de Onesender website te gaan. Meer informatie {0}",
"Add Remote Browser": "Externe browser toevoegen",
@@ -1551,5 +1551,27 @@
"expectedTlsAlertDescription": "Selecteer de TLS-waarschuwing waarvan u verwacht dat de server deze retourneert. Gebruik {code} om te verifiëren dat mTLS-eindpunten verbindingen weigeren zonder clientcertificaten. Zie {link} voor details.",
"Protocol": "Protocol",
"domain_expiry_unsupported_missing_target": "Geen geldige domeinnaam of hostnaam is ingesteld voor deze monitor",
"Endpoint": "Endpoint"
"Endpoint": "Endpoint",
"signalUseTemplate": "Gebruik een persoonlijk berichtensjabloon",
"signalUseTemplateDescription": "Indien ingeschakeld, wordt het bericht verzonden met een persoonlijk sjabloon. U kunt Liquid-templating gebruiken om het meldingsformaat aan te passen.",
"360messengerAuthToken": "360messenger API Sleutel",
"360messengerRecipient": "Telefoonnummer (s) van de ontvanger",
"360messengerGroupId": "360messenger Groep ID",
"360messengerUseTemplate": "Gebruik een persoonlijk berichtensjabloon",
"360messengerTemplate": "360messenger berichtensjabloon",
"360messengerGroupList": "WhatsApp groepen",
"360messengerSelectGroupList": "Selecteer een groep om toe te voegen",
"360messengerSelectedGroupID": "Geselecteerde Groep ID(s)",
"360messengerEnableSendToGroup": "Schakel versturen naar WhatsApp groep(en) in",
"360messengerCustomMessageTemplate": "Persoonlijk berichtensjabloon",
"360messengerEnableCustomMessage": "Schakel een persoonlijk berichtensjabloon in i.p.v. het standaardbericht.",
"360messengerMessageTemplate": "Berichtensjabloon",
"teamsEnableTags": "Inclusief tags",
"teamsEnableTagsDescription": "Indien ingeschakeld zal het bericht de monitortags bevatten.",
"certificateExpiryNotificationHelp": "De hoeveelheid dagen op voorhand kan in de instellingen worden geconfigureerd.",
"matrixUseTemplate": "Gebruik een persoonlijk berichtensjabloon",
"matrixUseTemplateDescription": "Indien ingeschakeld, wordt het bericht verzonden met een persoonlijk sjabloon.",
"monitorTypeGameServer": "Spelserver",
"monitorTypeDatabase": "Databank Monitor Type",
"monitorTypeSpecial": "Speciaal"
}

View File

@@ -1351,8 +1351,8 @@
"invalidURL": "Nieprawidłowy adres URL",
"Resolve": "Rozwiązywanie",
"Resolved": "Rozwiązano",
"createdAt": "Utworzono: {data}",
"lastUpdatedAt": "Ostatnia aktualizacja: {data}",
"createdAt": "Utworzono: {date}",
"lastUpdatedAt": "Ostatnia aktualizacja: {date}",
"lastUpdatedAtFromNow": "Ostatnia aktualizacja: {date} ({fromNow})",
"descriptionHelpText": "Wyświetlane na wewnętrznym pulpicie nawigacyjnym. Markdown jest dozwolony i oczyszczany (zachowuje spacje i wcięcia) przed wyświetleniem.",
"Actions": "Akcje",

View File

@@ -1428,7 +1428,7 @@
"Monitors": "{n} Monitor | {n} Monitores",
"Suppress Notifications": "Suprimir notificações",
"discordSuppressNotificationsHelptext": "Quando ativada, as mensagens serão publicadas no canal, mas não acionarão notificações push ou notificações na área de trabalho para os destinatários.",
"domain_expiry_unsupported_is_icann": "O domínio \"{domain}\" não é um candidato para monitoramento de expiração de domínio, porque seu sufixo público \".{publicSuffix}\" não é ICAN",
"domain_expiry_unsupported_is_icann": "O domínio \"{domain}\" não é candidato ao monitoramento de expiração de domínio, pois seu sufixo público \".{publicSuffix}\" não é gerenciado pela ICANN",
"versionIs": "Versão: {version}",
"logoutCurrentUser": "Sair da sessão {username}",
"lastUpdatedAt": "Última atualização: {date}",
@@ -1553,5 +1553,39 @@
"RegexMatch": "Insira uma expressão regular para corresponder ao valor do registro",
"GlobalpingMonitorDescription": "O Globalping oferece acesso a milhares de sondas hospedadas pela comunidade para executar testes e medições de rede. Há um limite de 250 testes por hora para todos os usuários anônimos. Para dobrar o limite para 500 por hora, salve seu token em {accountSettings}. Consulte a documentação {docs} para obter mais informações.",
"certificateExpiryNotificationHelp": "O número de dias de antecedência pode ser configurado nas Configurações.",
"domainExpiryNotificationHelp": "O número de dias de antecedência pode ser configurado nas Configurações."
"domainExpiryNotificationHelp": "O número de dias de antecedência pode ser configurado nas Configurações.",
"signalUseTemplate": "Use um modelo de mensagem personalizado",
"signalUseTemplateDescription": "Se ativada, a mensagem será enviada usando um modelo personalizado. Você pode usar a linguagem de modelos Liquid para personalizar o formato da notificação.",
"monitorTypeGameServer": "Servidor de jogos",
"monitorTypeDatabase": "Tipo de monitor de banco de dados",
"monitorTypeSpecial": "Especial",
"360messengerRecipient": "Número(s) de telefone do destinatário",
"360messengerGroupId": "ID do grupo 360messenger",
"360messengerTemplate": "Modelo de mensagem do 360messenger",
"360messengerGroupList": "grupos do WhatsApp",
"360messengerSelectedGroupID": "ID(s) do(s) grupo(s) selecionado(s)",
"360messengerEnableSendToGroup": "Ativar o envio para grupos do WhatsApp",
"360messengerCustomMessageTemplate": "Modelo de mensagem personalizado",
"360messengerMessageTemplate": "Modelo de mensagem",
"360messengerWayToGetUrlAndToken": "Você pode obter sua chave de API do 360messenger em {0}.",
"360messengerErrorNoApiKey": "Por favor, insira primeiro sua chave de API do 360messenger.",
"360messengerErrorNoGroups": "Não foram encontrados grupos do WhatsApp para esta conta.",
"360messengerErrorGeneric": "Não foi possível carregar a lista de grupos do WhatsApp: {message}",
"360messengerAuthToken": "Chave da API do 360messenger",
"360messengerUseTemplate": "Use um modelo de mensagem personalizado",
"360messengerSelectGroupList": "Selecione um grupo para adicionar",
"360messengerEnableCustomMessage": "Ative um modelo de mensagem personalizado em vez da mensagem padrão.",
"360messengerWayToWriteRecipient": "Insira um ou mais números de telefone no formato internacional, sem o sinal de mais inicial (por exemplo, {0}). Separe vários números com vírgulas.",
"360messengerErrorApi": "Não foi possível carregar a lista de grupos do WhatsApp (Erro {statusCode}: {message}).",
"GlobalpingMultipleLocationsError": "Não é possível realizar várias localizações; utilize uma única localização para cada monitor.",
"GlobalpingLocationDescription": "O campo de localização aceita continentes, países, regiões, cidades, ASNs, ISPs ou regiões de nuvem. Você pode combinar filtros com {plus} (por exemplo, {amazonPlusGermany} ou {comcastPlusCalifornia}). Se a latência for uma métrica importante, use filtros para restringir a localização a uma região pequena para evitar picos e, para maior estabilidade, defina o filtro {datacenter}. {fullDocs}.",
"fluxerMessageFormat": "Formato da mensagem",
"fluxerMessageFormatNormal": "Normal (rich embeds)",
"fluxerMessageFormatMinimalist": "Minimalista (status curto)",
"fluxerUseMessageTemplate": "Use um modelo de mensagem personalizado",
"fluxerMessageTemplate": "Modelo de mensagem",
"fluxerMessageFormatCustom": "Modelo personalizado",
"fluxerUseMessageTemplateDescription": "Se ativada, a mensagem será enviada usando um modelo personalizado (LiquidJS). Deixe em branco para usar o formato padrão do Uptime Kuma.",
"Fluxer Webhook URL": "URL do Webhook do Fluxer",
"wayToGetFluxerURL": "Você pode obter essa informação acessando as configurações do canal de destino > Webhooks > Criar Webhook > Copiar URL do Webhook."
}

View File

@@ -480,5 +480,24 @@
"-year": "-ano",
"Json Query Expression": "Expressão Json Query",
"ignoredTLSError": "Erros TLS/SSL foram ignorados",
"Clone Maintenance": ""
"Clone Maintenance": "",
"monitorTypeGameServer": "Servidor de Jogos",
"monitorTypeSpecial": "Especial",
"mariadbCaCertificateHelptext": "Cole o Certificado da AC em formato PEM para utilizar com certificados autoassinados. Deixe em branco se a sua base de dados utilizar um certificado assinado por uma AC pública.",
"enableSSL": "Ativar SSL/TLS",
"Load More": "Ver Mais",
"mariadbUseSSLHelptext": "Permite utilizar uma ligação encriptada com a sua base de dados. Obrigatório para a maioria das bases de dados na cloud.",
"Loading...": "A carregar…",
"days": "{n} dia | {n} dias",
"hours": "{n} hora | {n} horas",
"mariadbCaCertificateLabel": "Certificado da AC",
"minutes": "{n} minuto | {n} minutos",
"minuteShort": "{n} min | {n} min",
"years": "{n} ano | {n} anos",
"Only retry if status code check fails": "Repete apenas se o código de estado indicar falha",
"No incidents recorded": "Nenhum incidente registado",
"versionIs": "Versão: {version}",
"Pin this incident": "Fixar este incidente",
"now": "agora",
"time ago": "Há {0}"
}

View File

@@ -14,7 +14,7 @@
"Maintenance": "Mentenanță",
"General Monitor Type": "Monitor de Tip General",
"Passive Monitor Type": "Monitor de Tip Pasiv",
"markdownSupported": "Limbaj Markdown acceptat",
"markdownSupported": "Se acceptă sintaxa Markdown. Dacă folosești HTML, evită spațiile la început pentru a preveni probleme de formatare.",
"Pause": "Pauză",
"Name": "Nume",
"Status": "Status",
@@ -571,7 +571,7 @@
"apiCredentials": "Credențiale API",
"octopushLegacyHint": "Folosiți versiunea veche a Octopush (2011-2020) sau versiunea nouă?",
"Check octopush prices": "Verifică prețurile octopush {0}.",
"octopushPhoneNumber": "Număr de telefon (format internațional, ex : +33612345678 ",
"octopushPhoneNumber": "Număr de telefon (format internațional, ex : +33612345678)",
"LunaSea Device ID": "ID Dispozitiv LunaSea",
"octopushTypePremium": "Premium (Rapid - recomandat pentru alertare)",
"octopushTypeLowCost": "Cost scăzut (Încet - câteodată este blocat de operator)",
@@ -594,7 +594,7 @@
"Proto Service Name": "Nume serviciu Proto",
"Enable TLS": "Activați TLS",
"Economy": "Economie",
"SMSManager API Docs": "Documente API SMSManager ",
"SMSManager API Docs": "Documente API SMSManager",
"Gateway Type": "Tip gateway",
"Base URL": "URL de Bază",
"PhoneNumbers": "NumereTelefon",
@@ -805,7 +805,7 @@
"Badge Warn Days": "Badge zile de avertizare",
"monitorToastMessagesDescription": "Notificările toast pentru monitoare dispar după un anumit timp în secunde. Setat la -1, dezactivează timpul de expirare. Setat la 0, dezactivează notificările toast.",
"noGroupMonitorMsg": "Nu este disponibil. Creați mai întâi un grup de monitoare.",
"wayToGetFlashDutyKey": "Puteți merge la Canal -> (Selectați un canal) -> Integrari -> Adăugați o nouă pagină de integrare, adăugați \"Uptime Kuma\" pentru a obține o adresă push, copiați cheia de integrare în adresă. Pentru mai multe informatii va rugam vizitati",
"wayToGetFlashDutyKey": "Pentru a integra Uptime Kuma cu Flashduty: accesează Channels > Selectează un canal > Integrations > Add a new integration, alege Uptime Kuma și copiază Push URL.",
"remoteBrowsersDescription": "Browserele Remote sunt o alternativă la rularea locală a Chromium. Configurați cu un serviciu precum browserless.io sau conectați-vă la cel personal",
"Remote Browsers": "Browsere Remote",
"Remote Browser": "Browser Remote",
@@ -834,7 +834,7 @@
"Kafka Topic Name": "Numele Subiectului Kafka",
"Kafka Producer Message": "Mesajul Producătorului Kafka",
"Enable Kafka SSL": "Activați SSL Kafka",
"liquidIntroduction": "Șablonarea se realizează prin intermediul limbajului de șabloane Liquid. Consultați {0} pentru instrucțiuni de utilizare. Acestea sunt variabilele disponibile:",
"liquidIntroduction": "Șablonarea se realizează prin intermediul limbajului de șabloane Liquid. Consultați {0} pentru instrucțiuni de utilizare.",
"smtpLiquidIntroduction": "Următoarele două câmpuri pot fi șablonate prin Limbajul de șabloane Liquid. Consultați {0} pentru instrucțiuni de utilizare. Acestea sunt variabilele disponibile:",
"Legacy Octopush-DM": "Octopush-DM vechi",
"alertaRecoverState": "Stare Recuperare",
@@ -1077,5 +1077,16 @@
"Separate multiple email addresses with commas": "Separați adresele de e-mail multiple cu virgule",
"Correct": "Corect",
"Fail": "Eșec",
"shrinkDatabaseDescriptionSqlite": "Declanșează comanda {vacuum} pentru baza de date SQLite. {auto_vacuum} este deja activat, dar acest lucru nu defragmentează baza de date și nici nu reîmpachetează paginile individuale ale bazei de date așa cum o face comanda {vacuum}."
"shrinkDatabaseDescriptionSqlite": "Declanșează comanda {vacuum} pentru baza de date SQLite. {auto_vacuum} este deja activat, dar acest lucru nu defragmentează baza de date și nici nu reîmpachetează paginile individuale ale bazei de date așa cum o face comanda {vacuum}.",
"monitorTypeGameServer": "Server Jocuri",
"monitorTypeDatabase": "Tip monitorizare bază de date",
"monitorTypeSpecial": "Special",
"Sender name": "Nume expeditor",
"smsplanetNeedToApproveName": "Trebuie să fie aprobat în panoul clientului.",
"mariadbUseSSLHelptext": "Activează utilizarea unei conexiuni criptate către baza ta de date. Este necesar pentru majoritatea bazelor de date din cloud.",
"mariadbCaCertificateHelptext": "Lipește certificatul CA în format PEM pentru a-l utiliza cu certificate self-signed. Lasă câmpul gol dacă baza ta de date folosește un certificat semnat de o autoritate de certificare publică.",
"mariadbCaCertificateLabel": "Certificat CA",
"No incidents recorded": "Nu au fost înregistrate incidente",
"Load More": "Încărcați mai mult",
"versionIs": "Versiune: {version}"
}

View File

@@ -50,7 +50,7 @@
"-hour": "-часа",
"Response": "Ответ",
"Ping": "Пинг",
"Monitor Type": "Тип Монитора",
"Monitor Type": "Тип монитора",
"Keyword": "Ключевое слово",
"Friendly Name": "Имя",
"URL": "URL-ссылка",
@@ -598,12 +598,12 @@
"critical": "критично",
"Custom Monitor Type": "Собственный тип монитора",
"markdownSupported": "Поддерживается синтаксис Markdown. Если используете HTML, избегайте пробелов в начале строки, чтобы не возникали проблемы с форматированием.",
"Passive Monitor Type": "Пассивный Тип Монитора",
"Specific Monitor Type": "Специфический Тип Монитора",
"Passive Monitor Type": "Пассивный тип монитора",
"Specific Monitor Type": "Специфический тип монитора",
"Help": "Помощь",
"Game": "Игра",
"Resend Notification if Down X times consequently": "Повторно отправить уведомление, если не работает X раз подряд",
"General Monitor Type": "Основной Тип Монитора",
"General Monitor Type": "Основной тип монитора",
"weekdayShortWed": "Ср",
"weekdayShortThu": "Чт",
"weekdayShortFri": "Пт",
@@ -1457,7 +1457,7 @@
"discordSuppressNotificationsHelptext": "Если включено, сообщения будут отправляться в канал, но не будут вызывать push- или настольные уведомления у получателей.",
"mtls-auth-server-cert-label": "Сертификат",
"mtls-auth-server-key-label": "Ключ",
"domain_expiry_unsupported_is_icann": "Домен «{domain}» не подходит для мониторинга срока действия, так как его публичный суффикс «.{publicSuffix}» не относится к ICANN",
"domain_expiry_unsupported_is_icann": "Домен \"{domain}\" не подходит для мониторинга срока действия, так как его публичный суффикс \".{publicSuffix}\" не управляется ICANN",
"mtls-auth-server-cert-placeholder": "Тело сертификата",
"systemService": "Системная служба",
"systemServiceName": "Имя службы",
@@ -1550,7 +1550,7 @@
"halopsa_field_timestamp": "Временная метка события в формате ISO 8601",
"halopsa_id_usage_hint": "💡 Совет: Используйте monitor_id для надежного сопоставления оповещений с заявками, а heartbeat_id — для отслеживания истории событий",
"halopsa_setup_step5": "Настройте сценарий автоматизации таким образом, чтобы он использовал monitor_id для сопоставления оповещений с существующими заявками",
"matrixUseTemplate": "Используйте собственный шаблон сообщения",
"matrixUseTemplate": "Использовать собственный шаблон сообщения",
"matrixUseTemplateDescription": "Если эта функция включена, сообщение будет отправлено с использованием пользовательского шаблона.",
"discordMessageFormat": "Формат Сообщения",
"discordMessageFormatNormal": "Обычный (расширенные встраивания)",
@@ -1572,5 +1572,49 @@
"discordUseMessageTemplate": "Использовать собственный шаблон сообщения",
"discordUseMessageTemplateDescription": "Если эта функция включена, сообщение будет отправлено с использованием пользовательского шаблона (LiquidJS). Оставьте поле пустым, чтобы использовать формат Uptime Kuma по умолчанию.",
"discordMessageTemplate": "Шаблон Сообщения",
"halopsa_field_uptime_kuma_version": "Номер версии Uptime Kuma"
"halopsa_field_uptime_kuma_version": "Номер версии Uptime Kuma",
"domainExpiryNotificationHelp": "Количество дней можно указать в настройках.",
"signalUseTemplateDescription": "Если включено, сообщение будет отправлено с использованием пользовательского шаблона. Вы можете использовать шаблоны Liquid для настройки формата уведомления.",
"RegexMatch": "Введите регулярное выражение, соответствующее значению записи",
"teltonikaUnsafeTlsDescription": "Отключение проверки TLS-сертификата делает вас уязвимым для атак типа \"человек посередине\" (man-in-the-middle), что может привести к утечке данных и компрометации систем. Не отключайте проверку сертификата, если вы не готовы принять этот риск. Рекомендуем использовать LetsEncrypt с функцией автоматического продления.",
"teltonikaUsernameHelptext": "Рекомендация: Создайте отдельную учётную запись, которая ограничена только отправкой SMS-сообщений, и введите здесь имя пользователя",
"teltonikaPasswordHelptext": "Вы можете задать пароль пользователя API в вашем роутере Teltonika, например, {0}",
"monitorTypeGameServer": "Игровой сервер",
"monitorTypeDatabase": "Тип монитора базы данных",
"monitorTypeSpecial": "Особый",
"GlobalpingMonitorDescription": "Globalping даёт возможность использовать тысячи зондов, размещённых сообществом, для тестирования и измерения параметров сети. Для анонимных пользователей действует ограничение — 250 тестов в час. Чтобы удвоить лимит до 500 тестов в час, сохраните свой токен в {accountSettings}. Подробнее смотрите в {docs}.",
"Teltonika SMS Gateway": "SMS-шлюз Teltonika",
"teltonikaVersionWarning": "Этот поставщик уведомлений требует, чтобы на вашем устройстве Teltonika был RMS версии 7.14.0 или выше.",
"teltonikaUrl": "URL-адрес вашего устройства Teltonika",
"signalUseTemplate": "Использовать пользовательский шаблон сообщения",
"RecordMatch": "Совпадение значения записи",
"teltonikaUnsafeTls": "Игнорировать проверку сертификата",
"teltonikaUsername": "Имя пользователя API",
"teltonikaUrlHelptext": "URL-адрес должен быть указан полностью, например {0} или {1}.",
"teltonikaPassword": "Пароль API",
"teltonikaModem": "Id модема",
"teltonikaModemHelptext": "Id SMS-модема должен быть в формате {0}. Инструкции приведены в https://developers.teltonika-networks.com/reference/.",
"teltonikaPhoneNumber": "Номер телефона",
"teltonikaPhoneNumberHelptext": "Номер должен быть в международном формате {0}, {1}. Допускается только один номер.",
"certificateExpiryNotificationHelp": "Количество дней можно указать в настройках.",
"360messengerAuthToken": "Ключ API 360messenger",
"360messengerRecipient": "Номер(а) получателя",
"360messengerGroupId": "ID группы 360messenger",
"360messengerUseTemplate": "Использовать пользовательский шаблон сообщения",
"360messengerTemplate": "Шаблон сообщения 360messenger",
"360messengerGroupList": "Группы WhatsApp",
"360messengerSelectGroupList": "Выберите группу для добавления",
"360messengerSelectedGroupID": "ID выбранных групп",
"360messengerEnableSendToGroup": "Включить отправку в группы WhatsApp",
"360messengerCustomMessageTemplate": "Пользовательский шаблон сообщения",
"360messengerMessageTemplate": "Шаблон сообщения",
"360messengerWayToGetUrlAndToken": "Вы можете получить ключ API 360messenger из {0}.",
"360messengerWayToWriteRecipient": "Введите один или несколько телефонных номеров в международном формате без начального знака плюс (например, {0}). Разделите несколько номеров запятыми.",
"360messengerErrorNoApiKey": "Пожалуйста, сначала введите ключ API 360messenger.",
"360messengerErrorNoGroups": "Для этой учётной записи не было найдено ни одной группы WhatsApp.",
"360messengerErrorApi": "Не удалось загрузить список групп WhatsApp (Ошибка {statusCode}: {message}).",
"360messengerErrorGeneric": "Не удалось загрузить список групп WhatsApp: {message}",
"360messengerEnableCustomMessage": "Включить пользовательский шаблон сообщения вместо сообщения по умолчанию.",
"GlobalpingMultipleLocationsError": "Несколько местоположений не поддерживаются, пожалуйста, используйте одно местоположение для каждого монитора.",
"GlobalpingLocationDescription": "В поле \"Местоположение\" можно указать континенты, страны, регионы, города, ASN, интернет-провайдеров или облачные регионы. Фильтры можно комбинировать с помощью символа {plus} (например, {amazonPlusGermany} или {comcastPlusCalifornia}). Если задержка является важным показателем, используйте фильтры, чтобы сузить местоположение до небольшого региона, чтобы избежать всплесков, а для большей стабильности установите фильтр {datacenter}. {fullDocs}."
}

View File

@@ -1431,7 +1431,7 @@
"legacyOctopushEndpoint": "Staršia verzia Octopush-DM (koncový bod: {url})",
"Suppress Notifications": "Stlmiť oznámenia",
"discordSuppressNotificationsHelptext": "Ak je táto funkcia povolená, správy budú odosielané do kanála, ale nebudú spúšťať push alebo desktopové notifikácie pre príjemcov.",
"domain_expiry_unsupported_is_icann": "Doména „{domain}“ nie je kandidátom na monitorovanie vypršania platnosti domény, pretože jej verejná prípona „.{publicSuffix}“ nie je ICAN",
"domain_expiry_unsupported_is_icann": "Doména „{domain}“ nie je kandidátom na monitorovanie vypršania platnosti domény, pretože jej verejná prípona „.{publicSuffix}“ nie je spravovaná organizáciou ICANN",
"snmpV3Username": "Používateľské meno SNMPv3",
"WeCom Mentioned Mobile List Description": "Zadajte telefónne čísla, ktoré chcete označiť. Viac čísel oddeľte čiarkami. Použite {'@'}all, aby ste označili všetkých.",
"WeCom Mentioned Mobile List": "WeCom zoznam zmienených",
@@ -1542,5 +1542,32 @@
"teltonikaUsernameHelptext": "Odporúčanie: Vytvorte samostatný účet, ktorý je obmedzený iba na odosielanie SMS správ, a zadajte sem jeho používateľské meno",
"teltonikaPhoneNumberHelptext": "Číslo musí byť v medzinárodnom formáte {0}, {1}. Je povolené iba jedno číslo.",
"RegexMatch": "Zadajte regulárny výraz, ktorý zodpovedá hodnote záznamu",
"GlobalpingMonitorDescription": "Globalping poskytuje prístup k tisícom komunitných sond na vykonávanie sieťových testov a meraní. Pre všetkých anonymných používateľov je stanovený limit 250 testov za hodinu. Ak chcete tento limit zdvojnásobiť na 500 testov za hodinu, uložte si svoj token v {accountSettings}. Ďalšie informácie nájdete v {docs}."
"GlobalpingMonitorDescription": "Globalping poskytuje prístup k tisícom komunitných sond na vykonávanie sieťových testov a meraní. Pre všetkých anonymných používateľov je stanovený limit 250 testov za hodinu. Ak chcete tento limit zdvojnásobiť na 500 testov za hodinu, uložte si svoj token v {accountSettings}. Ďalšie informácie nájdete v {docs}.",
"certificateExpiryNotificationHelp": "Počet dní vopred je možné nastaviť v nastaveniach.",
"signalUseTemplate": "Použite vlastnú šablónu správy",
"signalUseTemplateDescription": "Ak je táto funkcia povolená, správa bude odoslaná pomocou vlastnej šablóny. Na prispôsobenie formátu oznámenia môžete použiť šablóny Liquid.",
"domainExpiryNotificationHelp": "Počet dní vopred je možné nastaviť v nastaveniach.",
"monitorTypeDatabase": "Typ monitoru databázy",
"monitorTypeGameServer": "Herný server",
"monitorTypeSpecial": "Špeciálny",
"360messengerAuthToken": "API kľúč 360messenger",
"360messengerRecipient": "Telefónne číslo/a príjemcu",
"360messengerGroupId": "360messenger ID skupiny",
"360messengerUseTemplate": "Použite vlastnú šablónu správy",
"360messengerTemplate": "Šablóna správy 360messenger",
"360messengerGroupList": "Skupiny WhatsApp",
"360messengerSelectGroupList": "Vyberte skupinu, ktorú chcete pridať",
"360messengerSelectedGroupID": "Vybrané ID skupiny/ín",
"360messengerEnableSendToGroup": "Povoliť odosielanie do skupín WhatsApp",
"360messengerCustomMessageTemplate": "Šablóna vlastnej správy",
"360messengerEnableCustomMessage": "Povoliť vlastnú šablónu správy namiesto predvolenej správy.",
"360messengerMessageTemplate": "Šablóna správy",
"360messengerWayToWriteRecipient": "Zadajte jedno alebo viacero telefónnych čísel v medzinárodnom formáte bez predpony plus (napr. {0}). Viacero čísel oddeľte čiarkami.",
"360messengerErrorNoApiKey": "Najskôr zadajte svoj API kľúč 360messenger.",
"360messengerErrorApi": "Nie je možné načítať zoznam skupín WhatsApp (Chyba {statusCode}: {message}).",
"360messengerErrorGeneric": "Nie je možné načítať zoznam skupín WhatsApp: {message}",
"GlobalpingMultipleLocationsError": "Viacnásobné polohy nie sú podporované, pre každý monitor použite jednu polohu.",
"360messengerWayToGetUrlAndToken": "API kľúč pre 360messenger môžete získať na {0}.",
"360messengerErrorNoGroups": "Pre tento účet neboli nájdené žiadne skupiny WhatsApp.",
"GlobalpingLocationDescription": "Do poľa polohy môžete zadávať kontinenty, krajiny, regióny, mestá, ASN, ISP alebo cloudové regióny. Filtre môžete kombinovať pomocou znaku {plus} (napr. {amazonPlusGermany} alebo {comcastPlusCalifornia}). Ak je dôležitým ukazovateľom latencia, použite filtre na zúženie polohy na malý región, aby ste sa vyhli výkyvom, a pre lepšiu stabilitu nastavte filter {datacenter}. {fullDocs}."
}

View File

@@ -109,7 +109,7 @@
"Specific Monitor Type": "Applikationsspecifik övervakartyp",
"Push URL": "Push URL",
"Passive Monitor Type": "Passiv övervakartyp",
"markdownSupported": "Stödjer markdown-syntax",
"markdownSupported": "Markdown-syntax stöds. Om du använder HTML, undvik inledande mellanslag för att förhindra formateringsproblem.",
"Heartbeat Retry Interval": "Omprövningsintervall",
"needPushEvery": "Hämta denna URL var {0} sekund.",
"pushOptionalParams": "Valfria parametrar: {0}",
@@ -373,7 +373,7 @@
"Domain": "Domän",
"Most likely causes:": "Störst troliga anledningar:",
"Coming Soon": "Kommer snart",
"liquidIntroduction": "Mallar är uppnådda med Liquids mallspråk. Hänvisa till [0] för användningsinstruktioner. Tillgängliga variabler är:",
"liquidIntroduction": "Mallar uppnås via mallspråket Liquid. Se {0} för användarinstruktioner.",
"emailTemplateStatus": "Status",
"templateHeartbeatJSON": "objekt beskrivande hjärtslag",
"templateLimitedToUpDownCertNotifications": "Endast tillgänglig för UPP/NER/Certifikat giltigt notifieringar",
@@ -473,7 +473,7 @@
"Learn More": "Läs mer",
"Add Another": "Lägg till en annan",
"apiKeyAddedMsg": "Din API-nyckel har lagts till. Vänligen notera den för detta visas inte igen.",
"octopushPhoneNumber": "Telefonnummer (int. format, ex: +33612345678) ",
"octopushPhoneNumber": "Telefonnummer (int. format, ex: +33612345678)",
"noOrBadCertificate": "Inget/ogiltigt certifikat",
"successBackupRestored": "Lyckades återställa säkerhetskopia.",
"GrafanaOncallUrl": "Grafana OnCall-URL",
@@ -722,7 +722,7 @@
"deleteRemoteBrowserMessage": "Är du säker att du vill radera denna fjärrwebbläsare för alla övervakare?",
"Browser Screenshot": "Webbläsarskärmdump",
"enableProxyDescription": "Denna proxy påverkar inte övervakarnas förfrågan förräns den är aktiverad. Du kan kontrollera och tillfälligt inaktivera proxyn från alla övervakare genom aktiveringsstatus.",
"Long-Lived Access Token can be created by clicking on your profile name (bottom left) and scrolling to the bottom then click Create Token. ": "Långtidsaccess-token kan skapas genom att klicka på ditt profilnamn (nedre vänstra) och skrolla till botten och klicka på Skapa token. ",
"Long-Lived Access Token can be created by clicking on your profile name (bottom left) and scrolling to the bottom then click Create Token. ": "Långtidsaccess-token kan skapas genom att klicka på ditt profilnamn (nedre vänstra hörnet) och skrolla till botten och klicka på Skapa token.",
"Edit Maintenance": "Redigera underhåll",
"pushoversounds bugle": "Bugla",
"Enable TLS": "Aktivera TLS",
@@ -835,7 +835,7 @@
"Proto Method": "Proto-metod",
"Proto Content": "Proto-innehåll",
"SendKey": "Skickanyckel",
"SMSManager API Docs": "SMSManager API-dokument ",
"SMSManager API Docs": "SMSManager API-dokumentation",
"promosmsSMSSender": "SMS-avsändarnamn: Förregistrerat namn eller en av standard: InfoSMS, SMS Info, MaxSMS, INFO, SMS",
"Octopush API Version": "Octopuch API-version",
"Legacy Octopush-DM": "Äldre Octopush-DM",
@@ -1246,5 +1246,42 @@
"tagAlreadyStaged": "Den här taggen (namn och värde) är redan satt för den här batchen.",
"tagNameExists": "En systemtagg med det här namnet finns redan. Välj den från listan eller använd ett annat namn.",
"groupOnesenderDesc": "Se till att grupp-ID:t är giltigt. För att skicka meddelande till gruppen, t.ex.: 628123456789-342345",
"privateOnesenderDesc": "Se till att telefonnumret är giltigt. För att skicka meddelande till ett privat telefonnummer, t.ex.: 628123456789"
"privateOnesenderDesc": "Se till att telefonnumret är giltigt. För att skicka meddelande till ett privat telefonnummer, t.ex.: 628123456789",
"versionIs": "Version: {version}",
"enableSSL": "Aktivera SSL/TLS",
"mariadbUseSSLHelptext": "Aktivera för att använda en krypterad anslutning till din databas. Krävs för de flesta molndatabaser.",
"mariadbCaCertificateLabel": "CA Certifikat",
"mariadbCaCertificateHelptext": "Klistra in CA-certifikatet i PEM-format för att använda det med självsignerade certifikat. Lämna tomt om din databas använder ett certifikat som signerats av en offentlig CA.",
"unknownDays": "Okända dagar",
"No incidents recorded": "Inga incidenter registrerade",
"Load More": "Ladda mer",
"Loading...": "Laddar...",
"Monitors": "{n} Övervakare| {n} Övervakare",
"days": "{n} dag| {n} dagar",
"hours": "{n} timme | {n} timmar",
"minutes": "{n} minut | {n} minuter",
"minuteShort": "{n} min | {n} min",
"years": "{n} år | {n} år",
"Pin this incident": "Fäst denna incident",
"Only retry if status code check fails": "Försök igen bara om statuskodkontrollen misslyckas",
"retryOnlyOnStatusCodeFailureDescription": "Om aktiverat, kommer ett nytt försök endast göras när HTTP-statuskodkontrollen misslyckas (t.ex. servern är nere). Om statuskodkontrollen går igenom, men JSON-frågan misslyckas, markeras övervakaren omedelbart som nere utan omförsök.",
"wsCodeDescription": "För mer information om statuskoder, vänligen se {rfc6455}",
"Subprotocol(s)": "Delprotokoll(er)",
"saveResponseForNotifications": "Spara lyckade HTTP-svar för aviseringar",
"saveResponseDescription": "Lagrar HTTP-svaret och gör det tillgängligt för aviseringsmallar som {templateVariable}",
"responseMaxLength": "Svarets maxlängd (byte)",
"responseMaxLengthDescription": "Maximal storlek på svarsdata att lagra. Ställ in på 0 för obegränsad. Större svar kommer att avkortas. Standard: 1024 (1 KB)",
"logoutCurrentUser": "Logga ut {username}",
"Resolver Server(s)": "Resolver-server(ar)",
"Incident description": "Incidentbeskrivning",
"Incident not found or access denied": "Incidenten hittades inte eller åtkomst nekad",
"Past Incidents": "Tidigare incidenter",
"Incident title": "Incidenttitel",
"Pinned incidents are shown prominently on the status page": "Fästa incidenter visas tydligt på statussidan",
"Edit Incident": "Redigera incident",
"templateAvailableVariables": "Tillgängliga variabler",
"example": "Exempel",
"Result": "Resultat",
"HeadersInvalidFormatBecause": "Förfrågningsrubrikerna är inte giltiga JSON-filer på grund av {error}",
"saveErrorResponseForNotifications": "Spara HTTP-felsvar för aviseringar"
}

View File

@@ -1020,5 +1020,24 @@
"No incidents recorded": "ไม่มีเหตุการณ์ใดๆเกิดขึ้น",
"Load More": "โหลดเพิ่มเติม",
"Loading...": "กำลังโหลด...",
"Pin this incident": "ปักหมุดเหตุการณ์นี้"
"Pin this incident": "ปักหมุดเหตุการณ์นี้",
"Incident description": "คำอธิบายเหตุการณ์",
"Incident not found or access denied": "ไม่พบเหตุการณ์หรือเข้าถึงไม่ได้",
"Incident title": "ชื่อเหตุการณ์",
"tagAlreadyStaged": "แท็กนี้ (ชื่อและค่า) ได้ถูกเตรียมไว้สำหรับชุดนี้แล้ว",
"tagNameExists": "มีแท็กระบบที่มีชื่อนี้อยู่แล้ว เลือกจากรายการหรือใช้ชื่ออื่น",
"Edit Incident": "แก้ไขเหตุการณ์",
"templateAvailableVariables": "ตัวแปรที่ใช้งานได้",
"example": "ตัวอย่าง",
"Result": "ผลลัพธ์",
"HeadersInvalidFormatBecause": "ส่วนหัวของคำขอไม่ใช่ JSON ที่ถูกต้องเนื่องจาก {error}",
"BodyInvalidFormatBecause": "ส่วนตัวของคำขอไม่ใช่ JSON ที่ถูกต้องเนื่องจาก {error}",
"mqttWebSocketPath": "MQTT WebSocket Path",
"mqttWebsocketPathExplanation": "เส้นทาง WebSocket สำหรับ MQTT ผ่านการเชื่อมต่อ WebSocket (เช่น /mqtt)",
"mqttWebsocketPathInvalid": "โปรดใช้รูปแบบเส้นทาง WebSocket ที่ถูกต้อง",
"mqttHostnameTip": "โปรดใช้รูปแบบนี้ {hostnameFormat}",
"hostnameCannotBeIP": "ชื่อโฮสต์ DNS ไม่สามารถเป็นที่อยู่ IP ได้ คุณต้องการใช้ช่องตัวแก้ไขใช่หรือไม่",
"Past Incidents": "เหตุการณ์ที่ผ่านมา",
"Pinned incidents are shown prominently on the status page": "เหตุการณ์ที่ปักหมุดไว้จะแสดงอย่างเด่นชัดบนหน้าสถานะ",
"steamApiKeyDescriptionAt": "ในการตรวจสอบสถานะเซิร์ฟเวอร์เกม Steam คุณต้องมีคีย์ Steam Web-API คุณสามารถลงทะเบียนคีย์ API ของคุณได้ที่ {url}"
}

View File

@@ -55,7 +55,7 @@
"Delete": "Xoá",
"Current": "Hiện tại",
"Uptime": "Uptime",
"Cert Exp.": "Hạn chứng chỉ",
"Cert Exp.": "Hạn chứng chỉ.",
"day": "ngày",
"-day": "-ngày",
"hour": "giờ",
@@ -472,7 +472,7 @@
"Maintenance": "Bảo trì",
"Affected Monitors": "Kênh theo dõi bị ảnh hưởng",
"Schedule maintenance": "Thêm lịch bảo trì",
"markdownSupported": "Có hỗ trợ cú pháp Markdown",
"markdownSupported": "Hỗ trợ cú pháp Markdown. Nếu sử dụng HTML, hãy tránh khoảng trắng đầu dòng để tránh các vấn đề về định dạng.",
"Start of maintenance": "Bắt đầu bảo trì",
"All Status Pages": "Tất cả các trang trạng thái",
"Select status pages...": "Chọn trang trạng thái…",
@@ -555,5 +555,80 @@
"selectedMonitorCount": "Đã chọn: {0}",
"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": "nhập hostname của server bạn muốn kết nối hoặc {localhost} nếu bạn muốn dùng {local_mta}",
"webhookBodyPresetOption": "Cài đặt sẵn",
"critical": "nguy kịch"
"critical": "nguy kịch",
"Google": "Google",
"Template Format": "Định dạng mẫu",
"YZJ Robot Token": "Mã thông báo Robot YZJ",
"YZJ Webhook URL": "URL Webhook YZJ",
"discordMessageFormatCustom": "Mẫu tùy chỉnh",
"discordMessageFormatMinimalist": "Nhỏ nhất (trạng thái gọn)",
"discordMessageFormatNormal": "Thông thường (nhúng đa dạng)",
"Suppress Notifications": "Chặn thông báo",
"Installing a Nextcloud Talk bot requires administrative access to the server.": "Việc cài đặt bot Nextcloud Talk yêu cầu quyền quản trị máy chủ.",
"Send DOWN silently": "Gửi thông báo DOWN một cách im lặng",
"Send UP silently": "Gửi trạng thái UP một cách yên lặng",
"Bot secret": "Mã bí mật của bot",
"Conversation token": "Token trao đổi",
"Nextcloud host": "Máy chủ Nextcloud",
"Manual": "Thủ công",
"Staged Tags for Batch Add": "Thẻ được thiết lập cho việc thêm hàng loạt",
"Add Another Tag": "Thêm thẻ mới",
"discordMessageTemplate": "Mẫu tin nhắn",
"Globalping API Token": "Mã thông báo API Globalping",
"domain_expiry_unsupported_is_icann": "Tên miền \"{domain}\" không phải là ứng cử viên cho việc giám sát hết hạn tên miền, vì hậu tố công khai \".{publicSuffix}\" của nó không phải là ICAN",
"Never": "Không bao giờ",
"discordMessageFormat": "Định dạng tin nhắn",
"Ip Family": "IP gia đình",
"Expected TLS Alert": "Cảnh báo TLS dự kiến",
"Basic radio toggle button group": "Nhóm nút chuyển đổi radio cơ bản",
"GlobalpingMonitorDescription": "Globalping cung cấp quyền truy cập vào hàng ngàn công cụ thăm dò do cộng đồng lưu trữ để chạy các bài kiểm tra và đo lường mạng. Giới hạn 250 bài kiểm tra mỗi giờ được đặt cho tất cả người dùng ẩn danh. Để tăng gấp đôi giới hạn lên 500 bài kiểm tra mỗi giờ, vui lòng lưu mã thông báo của bạn trong {accountSettings}. Kiểm tra {docs} để biết thêm thông tin.",
"Happy Eyeballs algorithm": "Thuật toán Happy Eyeballs",
"discordUseMessageTemplateDescription": "Nếu được bật, tin nhắn sẽ được gửi dùng 1 mẫu tùy chỉnh (LiquidJS), để trống để dùng cấu hình Uptime Kuma mặc định",
"discordUseMessageTemplate": "Dùng mẫu tin nhắn tùy chỉnh",
"discordSuppressNotificationsHelptext": "Khi được bật, tin nhắn sẽ được đăng lên kênh nhưng sẽ không kích hoạt thông báo đẩy hoặc thông báo trên màn hình cho người nhận.",
"smsplanetApiToken": "Mã thông báo cho API SMSPlanet",
"Font Twemoji by Twitter licensed under": "Phông chữ Twemoji của Twitter được cấp phép theo",
"wayToWriteWahaChatId": "Số điện thoại có mã vùng quốc tế nhưng không có dấu cộng ở đầu ({0}), ID Liên hệ ({1}) hoặc ID Nhóm ({2}). Thông báo được gửi đến ID Trò chuyện này từ Phiên WAHA.",
"Clear Form": "Xóa biểu mẫu",
"Disable URL in Notification": "Tắt URL trong Thông báo",
"Umami": "Umami",
"Matomo": "Matomo",
"Plausible": "Plausible",
"smsplanetNeedToApproveName": "Cần được chấp thuận từ bảng điều khiển",
"Sender name": "Tên người gửi",
"Phone numbers": "Số điện thoại",
"the smsplanet documentation": "tài liệu smsplanet",
"globalpingApiTokenDescription": "Nhận mã thông báo API Globalping của bạn tại {0}.",
"GlobalpingHostname": "Một mục tiêu đo lường có thể truy cập công khai. Thông thường là tên máy chủ hoặc địa chỉ IPv4/IPv6, tùy thuộc vào loại phép đo.",
"GlobalpingLocation": "Trường vị trí chấp nhận châu lục, quốc gia, khu vực, thành phố, ASN, ISP hoặc khu vực đám mây. Bạn có thể kết hợp các bộ lọc với {plus} (ví dụ: {amazonPlusGermany} hoặc {comcastPlusCalifornia}). Nếu độ trễ là một chỉ số quan trọng, hãy sử dụng bộ lọc để thu hẹp vị trí xuống một khu vực nhỏ hơn nhằm tránh các đỉnh điểm. {fullDocs}.",
"wayToGetClickSMSIRTemplateID": "Mẫu của bạn phải chứa trường {uptkumaalert}. Bạn có thể tạo mẫu mới {tại đây}.",
"halopsa_field_uptime_kuma_version": "Số phiên bản Uptime Kuma",
"teltonikaPhoneNumberHelptext": "Số phải ở định dạng quốc tế {0}, {1}. Chỉ được phép sử dụng một số.",
"Message Template": "Mẫu tin nhắn",
"Plain Text": "Văn bản thô",
"versionIs": "Phiên bản: {version}",
"enableSSL": "Bật SSL/TLS",
"mariadbUseSSLHelptext": "Cho phép sử dụng kết nối mã hóa với cơ sở dữ liệu của bạn. Cần thiết đối với hầu hết các cơ sở dữ liệu đám mây.",
"mariadbCaCertificateLabel": "Chứng chỉ CA",
"mariadbCaCertificateHelptext": "Dán chứng chỉ CA ở định dạng PEM để sử dụng với chứng chỉ tự ký. Để trống nếu cơ sở dữ liệu của bạn sử dụng chứng chỉ được ký bởi một CA công cộng.",
"unknownDays": "Ngày không rõ",
"No incidents recorded": "Không có sự cố nào được ghi nhận",
"Load More": "Tải Thêm",
"Loading...": "Đang tải...",
"Monitors": "{n} Trình theo dõi | {n} Trình theo dõi",
"days": "{n} ngày | {n} ngày",
"hours": "{n} giờ | {n} giờ",
"minutes": "{n} phút | {n} phút",
"minuteShort": "{n} phút | {n} phút",
"years": "{n} năm | {n} năm",
"Pin this incident": "Ghim sự cố này",
"Json Query Expression": "Biểu thức truy vấn JSON",
"defaultFriendlyName": "Trình theo dõi mới",
"locally configured mail transfer agent": "Tác nhân chuyển thư được cấu hình cục bộ",
"Path": "Đường dẫn",
"ipFamilyDescriptionAutoSelect": "Sử dụng {happyEyeballs} để xác định IP gia đình.",
"smsplanetApiDocs": "Thông tin chi tiết về cách lấy mã thông báo API có thể được tìm thấy trong {the_smsplanet_documentation}.",
"Globalping - Access global monitoring probes": "Globalping - Truy cập các thiết bị thăm dò giám sát toàn cầu",
"halopsa_setup_step4": "Chọn xác thực cơ bản và tạo tên người dùng và mật khẩu. Sau đó nhập hoặc dán tên người dùng và mật khẩu đó vào các trường kiểm tra phía trên",
"pause": "Tạm dừng"
}

View File

@@ -294,7 +294,7 @@
"apiCredentials": "API Credentials",
"octopushLegacyHint": "您是否在使用旧版本的 Octopush2011-2020",
"Check octopush prices": "查看 Octopush 的价格 {0}。",
"octopushPhoneNumber": "电话号码(国际通用格式,例如:+33612345678",
"octopushPhoneNumber": "手机号码(国际通用格式,例如:+33612345678",
"octopushSMSSender": "短信发送名称3-11 位大小写字母、数字和空格a-zA-Z0-9",
"LunaSea Device ID": "LunaSea 设备 ID",
"Apprise URL": "Apprise 网址",
@@ -320,7 +320,7 @@
"promosmsTypeFlash": "SMS FLASH - 消息会自动显示在收信人设备上。仅限波兰地区的收信人。",
"promosmsTypeFull": "SMS FULL - 高级短信,您可以使用您自己的发信人名称(需要先注册)。对于警报来说更可靠。",
"promosmsTypeSpeed": "SMS SPEED - 最高优先级。非常快速可靠,但更贵(大约两倍 SMS FULL 的价格)。",
"promosmsPhoneNumber": "电话号码(波兰地区收信人可以不填区号)",
"promosmsPhoneNumber": "手机号码(波兰地区收信人可以不填区号)",
"promosmsSMSSender": "短信发信人名称已注册的名称或以下默认值之一InfoSMS、SMS Info、MaxSMS、INFO、SMS",
"Feishu": "飞书",
"Feishu WebHookUrl": "飞书 WebHook URL",
@@ -388,10 +388,10 @@
"serwersms": "SerwerSMS.pl",
"serwersmsAPIUser": "API 用户名(包括 webapi_ 前缀)",
"serwersmsAPIPassword": "API 密码",
"serwersmsPhoneNumber": "电话号码",
"serwersmsPhoneNumber": "手机号码",
"serwersmsSenderName": "SMS 发信人名称(需要在客户中心注册)",
"smseagle": "SMSEagle",
"smseagleTo": "电话号码",
"smseagleTo": "手机号码",
"smseagleGroup": "通讯录群组名",
"smseagleContact": "通讯录联系人",
"smseagleRecipientType": "收信人类型",
@@ -706,7 +706,7 @@
"Body Encoding": "请求体编码",
"telegramSendSilentlyDescription": "静默地发送消息。消息发布后用户会收到无声通知。",
"telegramProtectContent": "阻止转发/保存",
"telegramProtectContentDescription": "如果启用Telegram 中的机器人消息将受到保护,不会被转发和保存。",
"telegramProtectContentDescription": "启用Telegram 中的机器人消息将受到保护,无法被转发和保存。",
"Clone Monitor": "克隆监控项",
"Clone": "克隆",
"cloneOf": "{0} 的克隆",
@@ -924,9 +924,9 @@
"wayToGetHeiiOnCallDetails": "如需了解如何获取 Trigger ID 和 API 密钥,请访问 {documentation}",
"documentationOf": "{0} 文档",
"gtxMessagingToHint": "国际通用格式,需要前导 \"+\" {e164}、{e212} 或 {e214} 格式)",
"From Phone Number / Transmission Path Originating Address (TPOA)": "发件人电话号码 / 传输路径起始地址TPOA",
"From Phone Number / Transmission Path Originating Address (TPOA)": "发件人手机号码 / 传输路径起始地址TPOA",
"gtxMessagingApiKeyHint": "你可以在此找到你的 API 密钥My Routing Accounts > Show Account Information > API Credentials > REST API (v2.x)",
"To Phone Number": "收件人电话号码",
"To Phone Number": "收件人手机号码",
"gtxMessagingFromHint": "在手机上,收件人会看到 TPOA 地址作为消息的发送者。TPOA 允许的格式包括至多11个字母或数字、短代码、当地长代码或国际号码{e164}、{e212} 或 {e214} 格式)",
"Allow Long SMS": "允许长消息",
"Destination": "收件人",
@@ -979,7 +979,7 @@
"smspartnerApiurl": "你可以在此处找到你的 API 密钥:{0}",
"smspartnerSenderNameInfo": "不能使用特殊字符,字符数在 3 到 11 个之间",
"smspartnerPhoneNumberHelptext": "号码必须使用国际通用格式,例如 {0}、{1}。多个号码必须使用 {2} 分隔",
"threemaRecipientTypePhone": "电话号码",
"threemaRecipientTypePhone": "手机号码",
"threemaRecipientTypeIdentityFormat": "8 位字符",
"threemaRecipientTypeIdentity": "Threema ID",
"threemaRecipientType": "收信人类型",
@@ -1082,7 +1082,7 @@
"Scifi": "Scifi科幻",
"Flute": "Flute长笛",
"Doorbell": "Doorbell门铃",
"The phone number of the recipient in E.164 format.": "收件人的 E.164 格式电话号码。",
"The phone number of the recipient in E.164 format.": "收件人的 E.164 格式手机号码。",
"Can be found on:": "可在此找到:{0}",
"From": "发件人",
"Time sensitive notifications will be delivered immediately, even if the device is in do not disturb mode.": "即使设备处于专注模式,即时通知也会立即发送。",
@@ -1104,16 +1104,16 @@
"templateStatus": "状态",
"templateHostnameOrURL": "主机名或 URL",
"templateServiceName": "服务名",
"telegramUseTemplateDescription": "如果启用该消息将使用自定义模板发送。",
"telegramUseTemplateDescription": "启用该消息将使用自定义模板发送。",
"telegramUseTemplate": "使用自定义消息模板",
"wayToGetWahaSession": "在此会话中WAHA 会向聊天 ID 发送通知。您可以在 WAHA 仪表板中找到它。",
"wayToGetWahaApiUrl": "你的 WAHA 实例 URL。",
"wahaChatId": "聊天 ID电话号码 / 联系人 ID / 群组 ID",
"wahaChatId": "聊天 ID手机号码 / 联系人 ID / 群组 ID",
"wahaSession": "会话",
"Template Format": "模板格式",
"Message Template": "消息模板",
"Plain Text": "纯文本",
"wayToWriteWahaChatId": "包含国际区号但不含开头加号({0})的电话号码、联系人 ID{1})、组 ID{2})。通知将从 WAHA 会话发送到此聊天 ID。",
"wayToWriteWahaChatId": "包含国际区号但不含开头加号({0})的手机号码、联系人 ID{1})、组 ID{2})。通知将从 WAHA 会话发送到此聊天 ID。",
"wayToGetWahaApiKey": "API 密钥是你用于运行 WAHA 的 WHATSAPP_API_KEY 环境变量值。",
"telegramTemplateFormatDescription": "Telegram 允许在消息中使用不同的标记语言,具体细节请参见 Telegram {0}。",
"YZJ Webhook URL": "YZJ Webhook 地址",
@@ -1385,7 +1385,7 @@
"aliyun-template-requirements-and-parameters": "阿里云短信模板必须包含以下变量:{parameters}",
"aliyun-template-optional-parameters": "可选变量:{parameters}",
"notificationSmsServices": "短信服务",
"ntfyCallHelptext": "报警触发时拨打电话。将电话号码设置为 yes 以使用你的首个已验证号码,也可设置为特定电话号码(如 +12223334444。需要 ntfy Pro 且电话号码已验证。",
"ntfyCallHelptext": "报警触发时拨打电话。将手机号码设置为 yes 以使用你的首个已验证号码,也可设置为特定手机号码(如 +12223334444。需要 ntfy Pro 且手机号码已验证。",
"domain_expiry_public_suffix_too_short": ".{publicSuffix} 过短而无法用作顶级域名",
"domain_expiry_unsupported_invalid_domain": "已设置的值 {hostname} 不是有效的域名",
"domain_expiry_unsupported_monitor_type": "该监控项类型不支持域名过期监控",
@@ -1454,7 +1454,7 @@
"saveErrorResponseForNotifications": "保存 HTTP 错误响应以用于通知",
"saveResponseForNotifications": "保存 HTTP 成功响应以用于通知",
"pausedMonitorsMsg": "暂停 {n} 个监控项",
"responseMaxLengthDescription": "响应数据可保存大小的最大值。设置为 0 表示不限制。超出该值的部分会被阶段。默认值1024即 1 KiB",
"responseMaxLengthDescription": "响应数据可保存大小的最大值。设置为 0 表示不限制。超出该值的部分会被截断。默认值1024即 1 KiB",
"Monitors": "{n} 个监控项",
"unknownDays": "未知天数",
"bulkDeleteErrorMsg": "删除 {n} 个监控项失败",
@@ -1473,7 +1473,7 @@
"frontendVersionIs": "前端版本:{version}",
"legacyOctopushEndpoint": "旧版 Octopush-DM地址{url}",
"octopushEndpoint": "octopush地址{url}",
"retryOnlyOnStatusCodeFailureDescription": "如果启用仅当 HTTP 状态码检查失败时(例如服务器宕机)才会进行重试。如果状态码检查通过但 JSON 查询失败,监控项将立即被标记为故障状态,不会进行重试。",
"retryOnlyOnStatusCodeFailureDescription": "启用仅当 HTTP 状态码检查失败时(例如服务器宕机)才会进行重试。如果状态码检查通过但 JSON 查询失败,监控项将立即被标记为故障状态,不会进行重试。",
"Only retry if status code check fails": "仅当状态码检查失败时重试",
"snmpV3Username": "SNMPv3 用户名",
"WeCom Mentioned Mobile List": "手机号列表",
@@ -1566,6 +1566,52 @@
"halopsa_setup_step5": "配置 runbook 以通过 monitor_id 将警报与现有工单进行匹配",
"halopsa_id_usage_hint": "💡提示:使用 monitor_id 可以可靠地将警报与工单匹配,如需跟踪事件历史记录则可用 heartbeat_id",
"halopsa_field_title": "警告标题(总是 \"Uptime Kuma Alert\"",
"matrixUseTemplateDescription": "如果启用该消息将使用自定义模板发送。",
"matrixUseTemplate": "使用自定义消息模板"
"matrixUseTemplateDescription": "启用该消息将使用自定义模板发送。",
"matrixUseTemplate": "使用自定义消息模板",
"Teltonika SMS Gateway": "Teltonika SMS 网关",
"RegexMatch": "输入正则表达式以筛选记录值",
"RecordMatch": "记录值筛选",
"teltonikaPassword": "API 密码",
"signalUseTemplate": "使用自定义消息模板",
"teltonikaPhoneNumber": "手机号码",
"teltonikaModemHelptext": "使用 {0} 格式的短信调制解调器 ID。参见 https://developers.teltonika-networks.com/reference/ 上的指引。",
"teltonikaModem": "调制解调器 ID",
"teltonikaPasswordHelptext": "可以在 Teltonika 路由里指定 API 用户密码,如 {0}",
"teltonikaUsername": "API 用户名",
"teltonikaUnsafeTls": "关闭证书验证",
"teltonikaUrlHelptext": "URL 应指定为完整来源,例如 {0} 或 {1}。",
"teltonikaUrl": "Teltonika 设备 URL",
"teltonikaVersionWarning": "此通知类型要求您的 Teltonika 设备运行的 RMS 为 7.14.0 或更高版本。",
"signalUseTemplateDescription": "启用后该消息将使用自定义模板发送。可以使用 Liquid 模板语法以自定义消息格式。",
"domainExpiryNotificationHelp": "提前天数可在设置中配置。",
"GlobalpingMonitorDescription": "Globalping 提供网络测试和 ping 测量服务,通过数千个来自社区的探测器。匿名用户每小时最多只能进行 250 次测试。要将限制提高到每小时 500 次,请将 token 保存在 {accountSettings} 中。更多信息请参阅 {docs}。",
"teltonikaPhoneNumberHelptext": "号码必须使用国际通用格式(如 {0}、{1})。仅限使用一个号码。",
"teltonikaUsernameHelptext": "建议:创建单独的仅用于发送短信的用户并在此填入其用户名",
"teltonikaUnsafeTlsDescription": "关闭 TLS 证书验证后容易受到中间人攻击,可能导致数据泄露和系统被入侵。除非能容忍这种攻击介质,否则请勿关闭证书验证。我们建议使用 Let's Encrypt 并启用自动续期功能。",
"certificateExpiryNotificationHelp": "提前天数可在设置中配置。",
"teamsEnableTagsDescription": "启用后消息将会包含监控项标签。",
"teamsEnableTags": "包含标签",
"360messengerErrorGeneric": "无法加载 WhatsApp 分组列表:{message}",
"360messengerErrorNoGroups": "该账号下未发现 WhatsApp 分组。",
"360messengerErrorNoApiKey": "请先输入你的 360messenger API 密钥。",
"360messengerWayToGetUrlAndToken": "可以从 {0} 获取 360messenger API 密钥。",
"360messengerMessageTemplate": "消息模板",
"360messengerCustomMessageTemplate": "自定义消息模板",
"360messengerEnableCustomMessage": "启用后将使用自定义消息模板以代替默认消息。",
"360messengerEnableSendToGroup": "启用后将发送至 WhatsApp 分组",
"360messengerSelectGroupList": "选择以添加一个分组",
"360messengerSelectedGroupID": "选择分组 ID",
"360messengerGroupList": "WhatsApp 分组",
"360messengerTemplate": "360messenger 消息模板",
"360messengerUseTemplate": "使用自定义消息模板",
"360messengerGroupId": "360messenger 分组 ID",
"360messengerRecipient": "收件人电话号码",
"360messengerAuthToken": "360messenger API 密钥",
"360messengerErrorApi": "无法加载 WhatsApp 分组列表(错误 {statusCode}{message})。",
"360messengerWayToWriteRecipient": "输入使用国际通用格式且不含前导 + 号的手机号(如:{0})。用逗号分隔多个号码。",
"monitorTypeGameServer": "游戏服务器",
"monitorTypeSpecial": "特殊",
"monitorTypeDatabase": "数据库监控项类型",
"GlobalpingMultipleLocationsError": "尚未支持多选区域,单个监控项请仅使用单个区域。",
"GlobalpingLocationDescription": "位置字段可接受大洲、国家/地区、区域、城市、ASN、ISP 或云区域。可通过 {plus} 将多个筛选条件结合起来使用(例如 {amazonPlusGermany} 或 {comcastPlusCalifornia})。如果延迟是重要指标,请使用筛选条件将位置范围缩小到较小的区域,以避免出现过高延迟。为了获得更好的稳定性,请设置 {datacenter} 筛选条件。{fullDocs}。"
}

View File

@@ -35,18 +35,12 @@
class="form-select"
data-testid="monitor-type-select"
>
<!-- Unsorted, since HTTP is commonly used -->
<optgroup :label="$t('General Monitor Type')">
<option value="group">
{{ $t("Group") }}
</option>
<option value="http">HTTP(s)</option>
<option value="keyword">HTTP(s) - {{ $t("Keyword") }}</option>
<option value="port">TCP Port</option>
<option value="ping">Ping</option>
<option value="smtp">SMTP</option>
<option value="snmp">SNMP</option>
<option value="keyword">HTTP(s) - {{ $t("Keyword") }}</option>
<option value="json-query">HTTP(s) - {{ $t("Json Query") }}</option>
<option value="grpc-keyword">gRPC(s) - {{ $t("Keyword") }}</option>
<option value="dns">DNS</option>
<option value="docker">
{{ $t("Docker Container") }}
@@ -63,7 +57,12 @@
<option value="real-browser">
HTTP(s) - Browser Engine (Chrome/Chromium) (Beta)
</option>
<option value="websocket-upgrade">Websocket Upgrade</option>
</optgroup>
<optgroup :label="$t('monitorTypeSpecial')">
<option value="group">
{{ $t("Group") }}
</option>
</optgroup>
<optgroup :label="$t('Passive Monitor Type')">
@@ -73,29 +72,43 @@
</option>
</optgroup>
<!-- Should sort from A to Z in this category -->
<optgroup :label="$t('Specific Monitor Type')">
<option value="globalping">
{{ $t("Globalping - Access global monitoring probes") }}
</option>
<option value="steam">
{{ $t("Steam Game Server") }}
</option>
<option value="gamedig">GameDig</option>
<option value="grpc-keyword">gRPC(s) - {{ $t("Keyword") }}</option>
<option value="json-query">HTTP(s) - {{ $t("Json Query") }}</option>
<option value="kafka-producer">Kafka Producer</option>
<option value="mqtt">MQTT</option>
<option value="rabbitmq">RabbitMQ</option>
<option value="kafka-producer">Kafka Producer</option>
<option value="sqlserver">Microsoft SQL Server</option>
<option value="postgres">PostgreSQL</option>
<option value="mysql">MySQL/MariaDB</option>
<option value="mongodb">MongoDB</option>
<option value="radius">Radius</option>
<option value="redis">Redis</option>
<option v-if="!$root.info.isContainer" value="sip-options">
SIP Options Ping
</option>
<option value="smtp">SMTP</option>
<option value="snmp">SNMP</option>
<option v-if="!$root.info.isContainer" value="tailscale-ping">
Tailscale Ping
</option>
<option value="websocket-upgrade">Websocket Upgrade</option>
</optgroup>
<!-- Should sort from A to Z in this category -->
<optgroup :label="$t('monitorTypeDatabase')">
<option value="sqlserver">Microsoft SQL Server</option>
<option value="mongodb">MongoDB</option>
<option value="mysql">MySQL/MariaDB</option>
<option value="postgres">PostgreSQL</option>
<option value="radius">Radius</option>
<option value="redis">Redis</option>
</optgroup>
<!-- Should sort from A to Z in this category -->
<optgroup :label="$t('monitorTypeGameServer')">
<option value="gamedig">GameDig</option>
<option value="steam">
{{ $t("Steam Game Server") }}
</option>
</optgroup>
</select>
<i18n-t
@@ -523,7 +536,7 @@
class="form-control"
required
/>
<i18n-t keypath="GlobalpingLocation" tag="div" class="form-text">
<i18n-t keypath="GlobalpingLocationDescription" tag="div" class="form-text">
<template #plus>
<code>+</code>
</template>
@@ -533,6 +546,9 @@
<template #comcastPlusCalifornia>
<code>comcast+california</code>
</template>
<template #datacenter>
<code>+datacenter</code>
</template>
<template #fullDocs>
<a
href="https://github.com/jsdelivr/globalping?tab=readme-ov-file#basic-location-targeting-"
@@ -1532,15 +1548,17 @@
v-model="monitor.domainExpiryNotification"
class="form-check-input"
type="checkbox"
:disabled="!hasDomain"
/>
<label class="form-check-label" for="domain-expiry-notification">
{{ $t("labelDomainNameExpiryNotification") }}
</label>
<div v-if="hasDomain" class="form-text">
<div class="form-text">
{{ $t("domainExpiryNotificationHelp") }}
</div>
<div v-if="!hasDomain && domainExpiryUnsupportedReason" class="form-text">
<div
v-if="monitor.domainExpiryNotification && domainExpiryUnsupportedReason"
class="form-text"
>
{{ domainExpiryUnsupportedReason }}
</div>
</div>
@@ -2820,11 +2838,22 @@ const toast = useToast();
const pushTokenLength = 32;
const defaultValueList = {
http: {
url: "https://",
accepted_statuscodes: ["200-299"],
},
"websocket-upgrade": {
url: "wss://",
accepted_statuscodes: ["1000"],
},
};
const monitorDefaults = {
type: "http",
name: "",
parent: null,
url: "https://",
url: defaultValueList.http.url,
wsSubprotocol: "",
method: "GET",
protocol: null,
@@ -2840,9 +2869,9 @@ const monitorDefaults = {
ignoreTls: false,
upsideDown: false,
expiryNotification: false,
domainExpiryNotification: false,
domainExpiryNotification: true,
maxredirects: 10,
accepted_statuscodes: ["200-299"],
accepted_statuscodes: defaultValueList.http.accepted_statuscodes,
saveResponse: false,
saveErrorResponse: true,
responseMaxLength: 1024,
@@ -2902,9 +2931,8 @@ export default {
notificationIDList: {},
// Do not add default value here, please check init() method
},
hasDomain: false,
domainExpiryUnsupportedReason: null,
checkMonitorDebounce: null,
checkDomainDebounce: null,
acceptedStatusCodeOptions: [],
acceptedWebsocketCodeOptions: [],
dnsresolvetypeOptions: [],
@@ -2963,16 +2991,6 @@ export default {
return this.$t("defaultFriendlyName");
},
monitorTypeUrlHost() {
const { type, url, hostname, grpcUrl } = this.monitor;
return {
type,
url,
hostname,
grpcUrl,
};
},
showDomainExpiryNotification() {
return this.monitor.type in TYPES_WITH_DOMAIN_EXPIRY_SUPPORT_VIA_FIELD;
},
@@ -3266,30 +3284,25 @@ message HealthCheckResponse {
}
},
monitorTypeUrlHost(data) {
if (this.checkMonitorDebounce != null) {
clearTimeout(this.checkMonitorDebounce);
}
showDomainExpiryNotification() {
this.checkDomain();
},
if (!this.showDomainExpiryNotification) {
this.hasDomain = false;
this.domainExpiryUnsupportedReason = null;
return;
}
"monitor.hostname"() {
this.checkDomain();
},
this.checkMonitorDebounce = setTimeout(() => {
this.$root.getSocket().emit("checkMointor", data, (res) => {
const wasSupported = this.hasDomain;
this.hasDomain = !!res?.ok;
if (this.hasDomain !== wasSupported) {
this.monitor.domainExpiryNotification = this.hasDomain;
}
this.domainExpiryUnsupportedReason = res.msgi18n ? this.$t(res.msg, res.meta) : res.msg;
});
}, 500);
"monitor.url"() {
this.checkDomain();
},
"monitor.grpcUrl"() {
this.checkDomain();
},
"monitor.type"(newType, oldType) {
this.checkDomain();
if (newType === "globalping" && !this.monitor.subtype) {
this.monitor.subtype = "ping";
}
@@ -3298,10 +3311,38 @@ message HealthCheckResponse {
this.monitor.dns_resolve_server = "1.1.1.1";
}
if (oldType && this.monitor.type === "websocket-upgrade") {
this.monitor.url = "wss://";
this.monitor.accepted_statuscodes = ["1000"];
// Change to websocket-upgrade (override http defaults)
if (newType === "websocket-upgrade") {
if (!this.monitor.url || this.monitor.url === defaultValueList.http.url) {
this.monitor.url = defaultValueList["websocket-upgrade"].url;
}
if (
!this.monitor.accepted_statuscodes ||
(this.monitor.accepted_statuscodes.length === 1 &&
this.monitor.accepted_statuscodes[0] === defaultValueList.http.accepted_statuscodes)
) {
this.monitor.accepted_statuscodes = defaultValueList["websocket-upgrade"].accepted_statuscodes;
}
}
// Change to http (override websocket-upgrade defaults)
// Because user may see wss:// and default to http code 1000, which is strange for http monitor.
if (["http", "keyword", "real-browser"].includes(newType)) {
if (!this.monitor.url || this.monitor.url === defaultValueList["websocket-upgrade"].url) {
this.monitor.url = defaultValueList.http.url;
}
if (
!this.monitor.accepted_statuscodes ||
(this.monitor.accepted_statuscodes.length === 1 &&
this.monitor.accepted_statuscodes[0] ===
defaultValueList["websocket-upgrade"].accepted_statuscodes)
) {
this.monitor.accepted_statuscodes = defaultValueList.http.accepted_statuscodes;
}
}
if (this.monitor.type === "push") {
if (!this.monitor.pushToken) {
// ideally this would require checking if the generated token is already used
@@ -3651,29 +3692,24 @@ message HealthCheckResponse {
}
}
// Validate Globalping location if present
if (this.monitor.type === "globalping" && this.monitor.location) {
if (this.monitor.location.includes(",")) {
toast.error(this.$t("GlobalpingMultipleLocationsError"));
return false;
}
}
// Validate hostname field input for various monitors
if (
[
"mqtt",
"dns",
"port",
"ping",
"steam",
"gamedig",
"radius",
"tailscale-ping",
"smtp",
"snmp",
].includes(this.monitor.type) &&
["dns", "port", "ping", "steam", "gamedig", "radius", "tailscale-ping", "smtp", "snmp"].includes(
this.monitor.type
) &&
this.monitor.hostname
) {
let hostname = this.monitor.hostname.trim();
if (this.monitor.type === "mqtt") {
hostname = hostname.replace(/^(mqtt|ws)s?:\/\//, "");
}
if (this.monitor.type === "dns" && isIP(hostname)) {
if (this.monitor.type === "dns" && this.monitor.dns_resolve_type !== "PTR" && isIP(hostname)) {
toast.error(this.$t("hostnameCannotBeIP"));
return false;
}
@@ -3699,30 +3735,42 @@ message HealthCheckResponse {
}
}
// Validate URL field input for various monitors
if (
["http", "keyword", "json-query", "websocket-upgrade", "real-browser"].includes(this.monitor.type) &&
this.monitor.url
) {
// monitor type : url protocol restrictions
// null is no restriction, as long as it is able to be parsed by new URL()
const acceptList = {
http: ["http:", "https:"],
keyword: ["http:", "https:"],
"json-query": ["http:", "https:"],
"websocket-upgrade": ["ws:", "wss:"],
"real-browser": null,
mqtt: ["mqtt:", "ws:", "wss:"],
};
if (this.monitor.type in acceptList) {
const allowedProtocols = acceptList[this.monitor.type];
try {
const url = new URL(this.monitor.url);
// Browser can encode *.hostname.com to %2A.hostname.com
if (url.hostname.includes("*") || url.hostname.includes("%2A")) {
toast.error(this.$t("wildcardOnlyForDNS"));
let url;
// Special handling for MQTT, because it was wrongly used hostname field to store the URL.
if (this.monitor.type === "mqtt") {
url = new URL(this.monitor.hostname);
} else {
url = new URL(this.monitor.url);
}
if (allowedProtocols && !allowedProtocols.includes(url.protocol)) {
console.log(url);
toast.error(this.$t("invalidURL"));
return false;
}
if (
!isFQDN(url.hostname, {
require_tld: false,
allow_underscores: true,
allow_trailing_dot: true,
}) &&
!isIP(url.hostname)
) {
toast.error(this.$t("invalidHostnameOrIP"));
// No empty hostname (mainly for non-http/ws URLs)
if (!url.host) {
toast.error(this.$t("invalidURL"));
return false;
}
} catch (err) {
} catch (e) {
toast.error(this.$t("invalidURL"));
return false;
}
@@ -3953,6 +4001,39 @@ message HealthCheckResponse {
}
}
},
// Check Domain
// Do nothing if not checked
checkDomain() {
console.log("checkDomain called");
if (this.checkDomainDebounce != null) {
clearTimeout(this.checkDomainDebounce);
}
if (!this.showDomainExpiryNotification) {
this.domainExpiryUnsupportedReason = null;
return;
}
this.checkDomainDebounce = setTimeout(() => {
const { type, url, hostname, grpcUrl } = this.monitor;
const data = {
type,
url,
hostname,
grpcUrl,
};
this.$root.getSocket().emit("checkDomain", data, (res) => {
console.log(data);
if (!res.ok) {
this.domainExpiryUnsupportedReason = res.msgi18n ? this.$t(res.msg, res.meta) : res.msg;
} else {
this.domainExpiryUnsupportedReason = null;
}
});
}, 500);
},
},
};
</script>

View File

@@ -156,7 +156,7 @@
class="form-select"
data-testid="analytics-type-select"
>
<option>{{ $t("None") }}</option>
<option :value="null">{{ $t("None") }}</option>
<option value="google">{{ $t("Google") }}</option>
<option value="umami">{{ $t("Umami") }}</option>
<option value="plausible">{{ $t("Plausible") }}</option>
@@ -688,7 +688,9 @@ export default {
enableEditMode: false,
enableEditIncidentMode: false,
hasToken: false,
config: {},
config: {
analyticsType: null,
},
selectedMonitor: null,
incident: null,
previousIncident: null,

View File

@@ -136,7 +136,7 @@ function ucfirst(str) {
}
exports.ucfirst = ucfirst;
function debug(msg) {
exports.log.log("", "debug", msg);
exports.log.log("", "DEBUG", msg);
}
exports.debug = debug;
class Logger {
@@ -167,7 +167,6 @@ class Logger {
return;
}
module = module.toUpperCase();
level = level.toUpperCase();
let now;
if (dayjs.tz) {
now = dayjs.tz(new Date()).format();
@@ -175,6 +174,30 @@ class Logger {
else {
now = dayjs().format();
}
if (process.env.UPTIME_KUMA_LOG_FORMAT === "json") {
const msgString = msg
.map((m) => {
if (typeof m === "string") {
return m;
}
else {
try {
return JSON.stringify(m);
}
catch (_a) {
return String(m);
}
}
})
.join(" ");
console.log(JSON.stringify({
time: now,
module: module,
level: level,
msg: msgString,
}));
return;
}
const levelColor = consoleLevelColors[level];
const moduleColor = consoleModuleColors[intHash(module, consoleModuleColors.length)];
let timePart;
@@ -218,19 +241,19 @@ class Logger {
}
}
info(module, ...msg) {
this.log(module, "info", ...msg);
this.log(module, "INFO", ...msg);
}
warn(module, ...msg) {
this.log(module, "warn", ...msg);
this.log(module, "WARN", ...msg);
}
error(module, ...msg) {
this.log(module, "error", ...msg);
this.log(module, "ERROR", ...msg);
}
debug(module, ...msg) {
this.log(module, "debug", ...msg);
this.log(module, "DEBUG", ...msg);
}
exception(module, exception, ...msg) {
this.log(module, "error", ...msg, exception);
this.log(module, "ERROR", ...msg, exception);
}
}
exports.log = new Logger();

View File

@@ -206,7 +206,7 @@ export function ucfirst(str: string) {
* @returns {void}
*/
export function debug(msg: unknown) {
log.log("", "debug", msg);
log.log("", "DEBUG", msg);
}
class Logger {
@@ -254,7 +254,7 @@ class Logger {
* @param msg Message to write
* @returns {void}
*/
log(module: string, level: string, ...msg: unknown[]) {
log(module: string, level: "INFO" | "WARN" | "ERROR" | "DEBUG", ...msg: unknown[]) {
if (level === "DEBUG" && !isDev) {
return;
}
@@ -264,7 +264,6 @@ class Logger {
}
module = module.toUpperCase();
level = level.toUpperCase();
let now;
if (dayjs.tz) {
@@ -273,6 +272,32 @@ class Logger {
now = dayjs().format();
}
if (process.env.UPTIME_KUMA_LOG_FORMAT === "json") {
const msgString = msg
.map((m) => {
if (typeof m === "string") {
return m;
} else {
try {
return JSON.stringify(m);
} catch {
return String(m);
}
}
})
.join(" ");
console.log(
JSON.stringify({
time: now,
module: module,
level: level,
msg: msgString,
})
);
return;
}
const levelColor = consoleLevelColors[level];
const moduleColor = consoleModuleColors[intHash(module, consoleModuleColors.length)];
@@ -329,7 +354,7 @@ class Logger {
* @returns {void}
*/
info(module: string, ...msg: unknown[]) {
this.log(module, "info", ...msg);
this.log(module, "INFO", ...msg);
}
/**
@@ -339,7 +364,7 @@ class Logger {
* @returns {void}
*/
warn(module: string, ...msg: unknown[]) {
this.log(module, "warn", ...msg);
this.log(module, "WARN", ...msg);
}
/**
@@ -349,7 +374,7 @@ class Logger {
* @returns {void}
*/
error(module: string, ...msg: unknown[]) {
this.log(module, "error", ...msg);
this.log(module, "ERROR", ...msg);
}
/**
@@ -359,7 +384,7 @@ class Logger {
* @returns {void}
*/
debug(module: string, ...msg: unknown[]) {
this.log(module, "debug", ...msg);
this.log(module, "DEBUG", ...msg);
}
/**
@@ -370,7 +395,7 @@ class Logger {
* @returns {void}
*/
exception(module: string, exception: unknown, ...msg: unknown[]) {
this.log(module, "error", ...msg, exception);
this.log(module, "ERROR", ...msg, exception);
}
}
@@ -418,7 +443,7 @@ export class TimeLogger {
* @param name Name of monitor
* @returns {void}
*/
print(name: string) {
print(name: string): void {
if (isDev && process.env.TIMELOGGER === "1") {
console.log(name + ": " + (dayjs().valueOf() - this.startTime) + "ms");
}

View File

@@ -96,38 +96,6 @@ describe("Domain Expiry", () => {
});
describe("Domain Parsing", () => {
test("throws error for IP address (isIp check)", async () => {
const monitor = {
type: "http",
url: "https://127.0.0.1",
domainExpiryNotification: true,
};
await assert.rejects(
async () => await DomainExpiry.checkSupport(monitor),
(error) => {
assert.strictEqual(error.constructor.name, "TranslatableError");
assert.strictEqual(error.message, "domain_expiry_unsupported_is_ip");
return true;
}
);
});
test("throws error for too short suffix(example.a)", async () => {
const monitor = {
type: "http",
url: "https://example.a",
domainExpiryNotification: true,
};
await assert.rejects(
async () => await DomainExpiry.checkSupport(monitor),
(error) => {
assert.strictEqual(error.constructor.name, "TranslatableError");
assert.strictEqual(error.message, "domain_expiry_public_suffix_too_short");
return true;
}
);
});
test("throws error for non-ICANN TLD (e.g. .local)", async () => {
const monitor = {
type: "http",

View File

@@ -165,6 +165,63 @@ describe("GlobalpingMonitorType", () => {
return true;
});
});
test("should retry create measurement on status 500", async () => {
const monitorType = new GlobalpingMonitorType("test-agent/1.0");
const mockClient = createGlobalpingClientMock();
const createResponse = createMockResponse({
id: "2g8T7V3OwXG3JV6Y10011zF2v",
});
const measurement = createPingMeasurement();
const awaitResponse = createMockResponse(measurement);
mockClient.createMeasurement.mock.mockImplementationOnce(() => ({
ok: false,
response: {
status: 500,
},
}));
mockClient.createMeasurement.mock.mockImplementation(() => createResponse);
mockClient.awaitMeasurement.mock.mockImplementation(() => awaitResponse);
const monitor = {
hostname: "example.com",
location: "North America",
ping_count: 3,
protocol: "ICMP",
ipFamily: "ipv4",
};
const heartbeat = {
status: PENDING,
msg: "",
ping: 0,
};
await monitorType.ping(mockClient, monitor, heartbeat, true);
const expectedCreateMeasurement = {
type: "ping",
target: "example.com",
inProgressUpdates: false,
limit: 1,
locations: [{ magic: "North America" }],
measurementOptions: {
packets: 3,
protocol: "ICMP",
ipVersion: 4,
},
};
assert.strictEqual(mockClient.createMeasurement.mock.calls.length, 2);
assert.deepStrictEqual(mockClient.createMeasurement.mock.calls[0].arguments[0], expectedCreateMeasurement);
assert.deepStrictEqual(mockClient.createMeasurement.mock.calls[1].arguments[0], expectedCreateMeasurement);
assert.deepStrictEqual(heartbeat, {
status: UP,
msg: "Ashburn (VA), US, NA, Amazon.com (AS14618), (aws-us-east-1) : OK",
ping: 2.169,
});
});
});
describe("http", () => {
@@ -579,6 +636,80 @@ describe("GlobalpingMonitorType", () => {
return true;
});
});
test("should retry create measurement on status 500", async () => {
const monitorType = new GlobalpingMonitorType("test-agent/1.0");
const mockClient = createGlobalpingClientMock();
const createResponse = createMockResponse({
id: "2g8T7V3OwXG3JV6Y10011zF2v",
});
const measurement = createHttpMeasurement();
const awaitResponse = createMockResponse(measurement);
mockClient.createMeasurement.mock.mockImplementationOnce(() => ({
ok: false,
response: {
status: 500,
},
}));
mockClient.createMeasurement.mock.mockImplementation(() => createResponse);
mockClient.awaitMeasurement.mock.mockImplementation(() => awaitResponse);
const monitor = {
url: "https://example.com:444/api/test?test=1",
location: "North America",
method: "GET",
accepted_statuscodes_json: JSON.stringify(["200-299", "300-399"]),
headers: '{"Test-Header": "Test-Value"}',
ipFamily: "ipv4",
dns_resolve_server: "8.8.8.8",
auth_method: "basic",
basic_auth_user: "username",
basic_auth_pass: "password",
};
const heartbeat = {
status: PENDING,
msg: "",
ping: 0,
};
await monitorType.http(mockClient, monitor, heartbeat, true);
const expectedToken = encodeBase64(monitor.basic_auth_user, monitor.basic_auth_pass);
const expectedCreateMeasurement = {
type: "http",
target: "example.com",
inProgressUpdates: false,
limit: 1,
locations: [{ magic: "North America" }],
measurementOptions: {
request: {
host: "example.com",
path: "/api/test",
query: "test=1",
method: "GET",
headers: {
"Test-Header": "Test-Value",
Authorization: `Basic ${expectedToken}`,
},
},
port: 444,
protocol: "HTTPS",
ipVersion: 4,
resolver: "8.8.8.8",
},
};
assert.strictEqual(mockClient.createMeasurement.mock.calls.length, 2);
assert.deepStrictEqual(mockClient.createMeasurement.mock.calls[0].arguments[0], expectedCreateMeasurement);
assert.deepStrictEqual(mockClient.createMeasurement.mock.calls[1].arguments[0], expectedCreateMeasurement);
assert.deepStrictEqual(heartbeat, {
status: UP,
msg: "New York (NY), US, NA, MASSIVEGRID (AS49683) : OK",
ping: 1440,
});
});
});
describe("dns", () => {
@@ -844,6 +975,73 @@ describe("GlobalpingMonitorType", () => {
["93.184.216.34", "1"],
]);
});
test("should retry create measurement on status 500", async () => {
const monitorType = new GlobalpingMonitorType("test-agent/1.0");
const mockClient = createGlobalpingClientMock();
const createResponse = createMockResponse({
id: "2g8T7V3OwXG3JV6Y10011zF2v",
});
const measurement = createDnsMeasurement();
const awaitResponse = createMockResponse(measurement);
mockClient.createMeasurement.mock.mockImplementationOnce(() => ({
ok: false,
response: {
status: 500,
},
}));
mockClient.createMeasurement.mock.mockImplementation(() => createMockResponse(createResponse));
mockClient.awaitMeasurement.mock.mockImplementation(() => awaitResponse);
const redbeanMock = createRedbeanMock();
redbeanMock.exec.mock.mockImplementation(() => Promise.resolve());
const monitor = {
id: "1",
hostname: "example.com",
location: "us-east-1",
dns_resolve_type: "A",
port: 53,
protocol: "udp",
};
const heartbeat = {
status: PENDING,
msg: "",
ping: null,
};
await monitorType.dns(mockClient, monitor, heartbeat, false, redbeanMock);
const expectedCreateMeasurement = {
type: "dns",
target: "example.com",
inProgressUpdates: false,
limit: 1,
locations: [{ magic: "us-east-1" }],
measurementOptions: {
query: { type: "A" },
port: 53,
protocol: "udp",
},
};
assert.strictEqual(mockClient.createMeasurement.mock.calls.length, 2);
assert.deepStrictEqual(mockClient.createMeasurement.mock.calls[0].arguments[0], expectedCreateMeasurement);
assert.deepStrictEqual(mockClient.createMeasurement.mock.calls[1].arguments[0], expectedCreateMeasurement);
assert.deepStrictEqual(heartbeat, {
status: UP,
msg: "New York (NY), US, NA, MASSIVEGRID (AS49683) : 93.184.216.34",
ping: 25,
});
assert.strictEqual(redbeanMock.exec.mock.calls.length, 1);
assert.deepStrictEqual(redbeanMock.exec.mock.calls[0].arguments, [
"UPDATE `monitor` SET dns_last_result = ? WHERE id = ? ",
["93.184.216.34", "1"],
]);
});
});
describe("helper methods", () => {

View File

@@ -0,0 +1,50 @@
const { describe, test } = require("node:test");
const assert = require("node:assert");
/**
* Extracts the ping value filtering logic from PingChart.vue pushDatapoint().
* This mirrors the condition: datapoint.up > 0 && datapoint.avgPing != null
* @param {object} datapoint Datapoint with up, avgPing, minPing, maxPing
* @returns {number|null} The avgPing value or null if filtered out
*/
function filterPingValue(datapoint) {
return datapoint.up > 0 && datapoint.avgPing != null ? datapoint.avgPing : null;
}
describe("PingChart pushDatapoint filtering", () => {
test("avgPing of 0 should be rendered, not filtered out (#7143)", () => {
const datapoint = { up: 1, down: 0, avgPing: 0, minPing: 0, maxPing: 0 };
const result = filterPingValue(datapoint);
assert.strictEqual(result, 0, "avgPing of 0 must not be converted to null");
});
test("avgPing of 1 should be rendered", () => {
const datapoint = { up: 1, down: 0, avgPing: 1, minPing: 1, maxPing: 1 };
const result = filterPingValue(datapoint);
assert.strictEqual(result, 1);
});
test("avgPing of null should be filtered out", () => {
const datapoint = { up: 1, down: 0, avgPing: null, minPing: null, maxPing: null };
const result = filterPingValue(datapoint);
assert.strictEqual(result, null);
});
test("avgPing of undefined should be filtered out", () => {
const datapoint = { up: 1, down: 0, avgPing: undefined, minPing: undefined, maxPing: undefined };
const result = filterPingValue(datapoint);
assert.strictEqual(result, null);
});
test("datapoint with no up counts should be filtered out", () => {
const datapoint = { up: 0, down: 1, avgPing: 5, minPing: 5, maxPing: 5 };
const result = filterPingValue(datapoint);
assert.strictEqual(result, null);
});
test("normal ping value with up count should be rendered", () => {
const datapoint = { up: 3, down: 0, avgPing: 42, minPing: 30, maxPing: 55 };
const result = filterPingValue(datapoint);
assert.strictEqual(result, 42);
});
});

View File

@@ -6,7 +6,7 @@ test.describe("Domain Expiry Notification", () => {
await restoreSqliteSnapshot(page);
});
test("supported TLD auto-enables checkbox", async ({ page }, testInfo) => {
test("TLD enabled for new monitor", async ({ page }, testInfo) => {
await page.goto("./add");
await login(page);
@@ -17,88 +17,6 @@ test.describe("Domain Expiry Notification", () => {
const checkbox = page.getByLabel("Domain Name Expiry Notification");
await expect(checkbox).toBeChecked();
await expect(checkbox).toBeEnabled();
await screenshot(testInfo, page);
});
test("unsupported TLD leaves checkbox disabled", async ({ page }, testInfo) => {
await page.goto("./add");
await login(page);
const monitorTypeSelect = page.getByTestId("monitor-type-select");
await monitorTypeSelect.selectOption("http");
await page.getByTestId("url-input").fill("https://example.co");
const checkbox = page.getByLabel("Domain Name Expiry Notification");
await expect(checkbox).not.toBeChecked();
await expect(checkbox).toBeDisabled();
await screenshot(testInfo, page);
});
test("switching from supported to unsupported TLD disables checkbox", async ({ page }, testInfo) => {
await page.goto("./add");
await login(page);
const monitorTypeSelect = page.getByTestId("monitor-type-select");
await monitorTypeSelect.selectOption("http");
const urlInput = page.getByTestId("url-input");
const checkbox = page.getByLabel("Domain Name Expiry Notification");
await urlInput.fill("https://example.com");
await expect(checkbox).toBeChecked();
await urlInput.fill("https://example.co");
await expect(checkbox).not.toBeChecked();
await expect(checkbox).toBeDisabled();
await screenshot(testInfo, page);
});
test("switching from unsupported to supported TLD enables checkbox", async ({ page }, testInfo) => {
await page.goto("./add");
await login(page);
const monitorTypeSelect = page.getByTestId("monitor-type-select");
await monitorTypeSelect.selectOption("http");
const urlInput = page.getByTestId("url-input");
const checkbox = page.getByLabel("Domain Name Expiry Notification");
await urlInput.fill("https://example.co");
await expect(checkbox).not.toBeChecked();
await urlInput.fill("https://example.com");
await expect(checkbox).toBeChecked();
await expect(checkbox).toBeEnabled();
await screenshot(testInfo, page);
});
test("manual uncheck preserved when URL changes within same TLD", async ({ page }, testInfo) => {
await page.goto("./add");
await login(page);
const monitorTypeSelect = page.getByTestId("monitor-type-select");
await monitorTypeSelect.selectOption("http");
const urlInput = page.getByTestId("url-input");
const checkbox = page.getByLabel("Domain Name Expiry Notification");
await urlInput.fill("https://example.com");
await expect(checkbox).toBeChecked();
await checkbox.uncheck();
await expect(checkbox).not.toBeChecked();
await urlInput.fill("https://example.com/different-path");
// Wait for debounce to fire and verify checkbox stays unchecked
await page.waitForTimeout(600);
await expect(checkbox).not.toBeChecked();
await expect(checkbox).toBeEnabled();
await screenshot(testInfo, page);
});