Compare commits

...

373 Commits

Author SHA1 Message Date
Louis Lam
4b913c8b4c Update to 1.22.0 2023-06-26 21:12:11 +08:00
Louis Lam
d01c7c3faa Update package-lock.json 2023-06-26 21:09:21 +08:00
Louis Lam
772a946234 Merge pull request #3242 from UptimeKumaBot/weblate-uptime-kuma-uptime-kuma
Translations Update from Weblate
2023-06-26 21:04:52 +08:00
Weblate
21405f71b5 Merge remote-tracking branch 'origin/master' 2023-06-26 04:54:08 +00:00
Louis Lam
b4b6e07e6b Merge pull request #3310 from chakflying/chore/auth-logging
Chore: Add logging for failed auth
2023-06-26 12:54:01 +08:00
Tarun Singh
cf4220901b Translated using Weblate (Hindi)
Currently translated at 5.4% (41 of 754 strings)

Co-authored-by: Tarun Singh <tarun7singh7@gmail.com>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/hi/
Translation: Uptime Kuma/Uptime Kuma
2023-06-26 04:52:40 +00:00
Buchtič
f3996fdef4 Translated using Weblate (Czech)
Currently translated at 100.0% (754 of 754 strings)

Co-authored-by: Buchtič <martin.buchta@gmail.com>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/cs/
Translation: Uptime Kuma/Uptime Kuma
2023-06-26 04:52:40 +00:00
CarlosCF
1dfe5227ad Translated using Weblate (Galician)
Currently translated at 2.7% (21 of 754 strings)

Added translation using Weblate (Galician)

Co-authored-by: CarlosCF <carloscaamano@gmail.com>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/gl/
Translation: Uptime Kuma/Uptime Kuma
2023-06-26 04:52:40 +00:00
SEOAlexRamon
4ead0609af Translated using Weblate (Catalan)
Currently translated at 3.4% (26 of 754 strings)

Translated using Weblate (Spanish)

Currently translated at 95.0% (717 of 754 strings)

Added translation using Weblate (Catalan)

Co-authored-by: SEOAlexRamon <seoalexramon@gmail.com>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/ca/
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/es/
Translation: Uptime Kuma/Uptime Kuma
2023-06-26 04:52:40 +00:00
401Unauthorized
a8bf52b1e0 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (754 of 754 strings)

Co-authored-by: 401Unauthorized <hi@4o1.to>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/zh_Hans/
Translation: Uptime Kuma/Uptime Kuma
2023-06-26 04:52:40 +00:00
Adam Stachowicz
ede6d90497 Translated using Weblate (Polish)
Currently translated at 96.6% (729 of 754 strings)

Co-authored-by: Adam Stachowicz <saibamenppl@gmail.com>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/pl/
Translation: Uptime Kuma/Uptime Kuma
2023-06-26 04:52:40 +00:00
Cyril59310
4b8e86efb7 Translated using Weblate (French)
Currently translated at 100.0% (754 of 754 strings)

Translated using Weblate (French)

Currently translated at 100.0% (753 of 753 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
2023-06-26 04:52:40 +00:00
Alex Javadi
5f706e1921 Translated using Weblate (Persian)
Currently translated at 100.0% (754 of 754 strings)

Translated using Weblate (Persian)

Currently translated at 100.0% (753 of 753 strings)

Co-authored-by: Alex Javadi <15309978+aljvdi@users.noreply.github.com>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/fa/
Translation: Uptime Kuma/Uptime Kuma
2023-06-26 04:52:40 +00:00
Vincent Houdan
722c64a4d1 Translated using Weblate (French)
Currently translated at 100.0% (752 of 752 strings)

Co-authored-by: Vincent Houdan <vincenthoudan@gmail.com>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/fr/
Translation: Uptime Kuma/Uptime Kuma
2023-06-26 04:52:40 +00:00
Rachatat Bunpat
23de52ca5a Translated using Weblate (Thai)
Currently translated at 85.3% (642 of 752 strings)

Co-authored-by: Rachatat Bunpat <rbunpat@gmail.com>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/th/
Translation: Uptime Kuma/Uptime Kuma
2023-06-26 04:52:40 +00:00
Yaroslav
3d3fb357f9 Translated using Weblate (Russian)
Currently translated at 100.0% (752 of 752 strings)

Co-authored-by: Yaroslav <ykargin@outlook.com>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/ru/
Translation: Uptime Kuma/Uptime Kuma
2023-06-26 04:52:40 +00:00
Tivin
3b9aa00126 Translated using Weblate (Hebrew (Israel))
Currently translated at 94.4% (710 of 752 strings)

Co-authored-by: Tivin <git@fickle.email>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/he_IL/
Translation: Uptime Kuma/Uptime Kuma
2023-06-26 04:52:39 +00:00
Saurabh
29267e5c2e Translated using Weblate (Hindi)
Currently translated at 1.3% (10 of 752 strings)

Added translation using Weblate (Hindi)

Co-authored-by: Saurabh <saurabhsharma2u@gmail.com>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/hi/
Translation: Uptime Kuma/Uptime Kuma
2023-06-26 04:52:39 +00:00
Ray
3e801323b6 Translated using Weblate (Chinese (Traditional))
Currently translated at 95.3% (716 of 751 strings)

Co-authored-by: Ray <ray7496422@gmail.com>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/zh_Hant/
Translation: Uptime Kuma/Uptime Kuma
2023-06-26 04:52:39 +00:00
b80fd81d24 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (751 of 751 strings)

Co-authored-by: 楓 <nitu2003@126.com>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/zh_Hans/
Translation: Uptime Kuma/Uptime Kuma
2023-06-26 04:52:39 +00:00
kosssi
9cb776405a Translated using Weblate (French)
Currently translated at 100.0% (751 of 751 strings)

Co-authored-by: kosssi <github@fafaru.com>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/fr/
Translation: Uptime Kuma/Uptime Kuma
2023-06-26 04:52:39 +00:00
Marco
de7ae3e2db Translated using Weblate (German)
Currently translated at 100.0% (754 of 754 strings)

Translated using Weblate (German (Switzerland))

Currently translated at 100.0% (754 of 754 strings)

Translated using Weblate (German (Switzerland))

Currently translated at 100.0% (751 of 751 strings)

Co-authored-by: Marco <marco@nanoweb.ch>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/de/
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/de_CH/
Translation: Uptime Kuma/Uptime Kuma
2023-06-26 04:52:39 +00:00
Michal
e49ced0524 Translated using Weblate (Czech)
Currently translated at 99.7% (752 of 754 strings)

Translated using Weblate (Czech)

Currently translated at 99.7% (750 of 752 strings)

Translated using Weblate (Czech)

Currently translated at 99.3% (746 of 751 strings)

Co-authored-by: Michal <black23@gmail.com>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/cs/
Translation: Uptime Kuma/Uptime Kuma
2023-06-26 04:52:39 +00:00
AnnAngela
7e782edf44 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (753 of 753 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (752 of 752 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (751 of 751 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (748 of 748 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
2023-06-26 04:52:39 +00:00
stanol
43e1e3c272 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (754 of 754 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (753 of 753 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (752 of 752 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (751 of 751 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (748 of 748 strings)

Co-authored-by: stanol <stanol777@gmail.com>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/uk/
Translation: Uptime Kuma/Uptime Kuma
2023-06-26 04:52:39 +00:00
Ömer Faruk Genç
dc4cf7087f Translated using Weblate (Turkish)
Currently translated at 100.0% (754 of 754 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (753 of 753 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (752 of 752 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (751 of 751 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (748 of 748 strings)

Co-authored-by: Ömer Faruk Genç <omer@farukgenc.com>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/tr/
Translation: Uptime Kuma/Uptime Kuma
2023-06-26 04:52:39 +00:00
Oleg Logvinov
65a0a2b2b5 Translated using Weblate (Russian)
Currently translated at 99.5% (745 of 748 strings)

Co-authored-by: Oleg Logvinov <oleglogwinow@gmail.com>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/ru/
Translation: Uptime Kuma/Uptime Kuma
2023-06-26 04:52:39 +00:00
ITQ
2d269c3639 Translated using Weblate (Russian)
Currently translated at 100.0% (754 of 754 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (752 of 752 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (751 of 751 strings)

Translated using Weblate (Russian)

Currently translated at 99.6% (748 of 751 strings)

Translated using Weblate (Russian)

Currently translated at 99.5% (745 of 748 strings)

Co-authored-by: ITQ <itq.dev@ya.ru>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/ru/
Translation: Uptime Kuma/Uptime Kuma
2023-06-26 04:52:39 +00:00
MrEddX
11bad53709 Translated using Weblate (Bulgarian)
Currently translated at 100.0% (754 of 754 strings)

Translated using Weblate (Bulgarian)

Currently translated at 100.0% (753 of 753 strings)

Translated using Weblate (Bulgarian)

Currently translated at 100.0% (752 of 752 strings)

Translated using Weblate (Bulgarian)

Currently translated at 100.0% (751 of 751 strings)

Translated using Weblate (Bulgarian)

Currently translated at 100.0% (748 of 748 strings)

Translated using Weblate (Bulgarian)

Currently translated at 99.8% (747 of 748 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
2023-06-26 04:52:39 +00:00
Arin Faraj
9f7782b1c1 Translated using Weblate (Kurdish (Central))
Currently translated at 5.8% (44 of 746 strings)

Co-authored-by: Arin Faraj <arin.abdul99@gmail.com>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/ckb/
Translation: Uptime Kuma/Uptime Kuma
2023-06-26 04:52:38 +00:00
Louis Lam
9fb8f94e22 Merge pull request #3311 from tarun7singh/monitor-group-fix
Added fix to remove children when type changed
2023-06-26 12:52:31 +08:00
Tarun Singh
7a34103da6 Added fix to remove children when type changed 2023-06-25 22:44:15 -04:00
Nelson Chan
cc94609423 Chore: Add logging for failed auth 2023-06-26 04:49:49 +08:00
Louis Lam
149f8c3646 Update required node version and update dependencies 2023-06-25 12:41:32 +08:00
Louis Lam
c06b929529 Update dependencies 2023-06-24 20:24:51 +08:00
Cyril59310
4e420ee3ff Missing translation key (#3281)
* missing translation key

* Update src/lang/en.json

Co-authored-by: Frank Elsinga <frank@elsinga.de>

---------

Co-authored-by: Louis Lam <louislam@users.noreply.github.com>
Co-authored-by: Frank Elsinga <frank@elsinga.de>
2023-06-19 13:17:58 +08:00
Louis Lam
6af44e0780 ChatGPT fixed my horrible grammar 2023-06-18 23:45:50 +08:00
Louis Lam
596402e71f Merge pull request #3246 from chakflying/ui/monitor-group-select
UI: Improve no group monitor message
2023-06-17 16:43:03 +08:00
Louis Lam
62bbc1cf55 Merge pull request #3270 from chakflying/fix/clone-group-monitor-fields
Fix: Remove extra fields on clone
2023-06-16 23:20:31 +08:00
Nelson Chan
19fc7d31e6 Fix: Remove extra fields on clone 2023-06-15 00:58:45 +08:00
Louis Lam
2b46693995 Merge pull request #3239 from madnight/master
Fix: prometheus monitor_status metric has 4 values
2023-06-13 23:09:29 +08:00
Louis Lam
c61a3d360f Merge pull request #3254 from CommanderStorm/transaltion_home
Chore: Added a translation key for "Home"
2023-06-13 23:05:02 +08:00
Louis Lam
392f95cdd2 Merge pull request #3260 from DevItq/master
Add translation for Not Found page
2023-06-13 23:04:04 +08:00
ITQ
dfc6e5ea5b Update NotFound.vue 2023-06-13 16:10:41 +03:00
Frank Elsinga
ba4d925374 Added a translation key for "Home" 2023-06-12 17:58:54 +02:00
Nelson Chan
c04194191f UI: improve disabled select legibility 2023-06-12 19:26:50 +08:00
Nelson Chan
de9ad0fe60 UI: Add help msg for no Group Mon. 2023-06-12 19:26:50 +08:00
Louis Lam
8884c2108b Merge pull request #3249 from CommanderStorm/translation_bugfix
Chore: Added additional translation keys
2023-06-12 14:07:19 +08:00
Frank Elsinga
ac8ca36895 Enabled adding missing keys to the translation database 2023-06-11 22:43:33 +02:00
Louis Lam
71c34694b7 Update to 1.22.0-beta.0 2023-06-11 15:17:26 +08:00
Louis Lam
2128ed5ce3 Update dependencies 2023-06-11 14:44:05 +08:00
Louis Lam
09ab6a015b Merge pull request #3046 from UptimeKumaBot/weblate-uptime-kuma-uptime-kuma
Translations Update from Weblate
2023-06-11 14:36:20 +08:00
Louis Lam
ec858eb67a Merge remote-tracking branch 'origin/master' into weblate-uptime-kuma-uptime-kuma
# Conflicts:
#	src/lang/de-DE.json
2023-06-11 14:35:10 +08:00
Louis Lam
c4c3fc81b2 Merge pull request #2693 from julian-piehl/group-monitors
Group monitors
2023-06-11 14:09:02 +08:00
Arin Faraj
8aa577529f Added translation using Weblate (Kurdish (Central))
Co-authored-by: Arin Faraj <arin.abdul99@gmail.com>
2023-06-10 20:28:20 +00:00
Manh PHam
cdd5067b17 Added translation using Weblate (Xhosa)
Co-authored-by: Manh PHam <manh.pham0997@gmail.com>
2023-06-10 20:28:20 +00:00
Oskar Fagerfjäll
5fdb01308a Translated using Weblate (Swedish)
Currently translated at 18.6% (139 of 746 strings)

Co-authored-by: Oskar Fagerfjäll <oskar.fagerfjall@gmail.com>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/sv/
Translation: Uptime Kuma/Uptime Kuma
2023-06-10 20:28:20 +00:00
Alexandre
eb1a1d0ac7 Translated using Weblate (Portuguese (Brazil))
Currently translated at 78.4% (585 of 746 strings)

Co-authored-by: Alexandre <alexandre@lopes.eng.br>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/pt_BR/
Translation: Uptime Kuma/Uptime Kuma
2023-06-10 20:28:19 +00:00
mottcha
a1fc283b3c Translated using Weblate (Japanese)
Currently translated at 69.0% (515 of 746 strings)

Translated using Weblate (Japanese)

Currently translated at 69.0% (515 of 746 strings)

Co-authored-by: mottcha <yuki627f@gmail.com>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/ja/
Translation: Uptime Kuma/Uptime Kuma
2023-06-10 20:28:19 +00:00
deluxghost
419b684433 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (746 of 746 strings)

Co-authored-by: deluxghost <deluxghost@gmail.com>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/zh_Hans/
Translation: Uptime Kuma/Uptime Kuma
2023-06-10 20:28:19 +00:00
Lê Huy Mạnh Tân
8bc139d8c1 Translated using Weblate (Vietnamese)
Currently translated at 62.1% (464 of 746 strings)

Co-authored-by: Lê Huy Mạnh Tân <tanmanh350@gmail.com>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/vi/
Translation: Uptime Kuma/Uptime Kuma
2023-06-10 20:28:19 +00:00
Artur Wróblewski
91dfd8dfaa Translated using Weblate (Polish)
Currently translated at 96.6% (721 of 746 strings)

Co-authored-by: Artur Wróblewski <krypalkora1984@gmail.com>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/pl/
Translation: Uptime Kuma/Uptime Kuma
2023-06-10 20:28:19 +00:00
Marco
1f405cf2a0 Translated using Weblate (German)
Currently translated at 100.0% (746 of 746 strings)

Translated using Weblate (German (Switzerland))

Currently translated at 100.0% (746 of 746 strings)

Co-authored-by: Marco <marco@nanoweb.ch>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/de/
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/de_CH/
Translation: Uptime Kuma/Uptime Kuma
2023-06-10 20:28:19 +00:00
Cyril59310
cf61077dd8 Translated using Weblate (French)
Currently translated at 99.8% (745 of 746 strings)

Translated using Weblate (French)

Currently translated at 97.7% (729 of 746 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
2023-06-10 20:28:19 +00:00
TKB Studios
4396e0d4d8 Translated using Weblate (French)
Currently translated at 100.0% (722 of 722 strings)

Co-authored-by: TKB Studios <alessio.dambrosio@outlook.com>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/fr/
Translation: Uptime Kuma/Uptime Kuma
2023-06-10 20:28:19 +00:00
Henry Wu
240db1d173 Translated using Weblate (Chinese (Traditional))
Currently translated at 94.0% (678 of 721 strings)

Co-authored-by: Henry Wu <me@henry40408.com>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/zh_Hant/
Translation: Uptime Kuma/Uptime Kuma
2023-06-10 20:28:19 +00:00
AnnAngela
ef06b5376d Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (746 of 746 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (746 of 746 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (721 of 721 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
2023-06-10 20:28:19 +00:00
deluxghost
186d733134 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (722 of 722 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (721 of 721 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (721 of 721 strings)

Co-authored-by: deluxghost <deluxghost@gmail.com>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/zh_Hans/
Translation: Uptime Kuma/Uptime Kuma
2023-06-10 20:28:19 +00:00
R1KO
225ba61e22 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (721 of 721 strings)

Co-authored-by: R1KO <r1kobeats@i.ua>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/uk/
Translation: Uptime Kuma/Uptime Kuma
2023-06-10 20:28:19 +00:00
Furkan İ
11b32ce553 Translated using Weblate (Turkish)
Currently translated at 100.0% (721 of 721 strings)

Co-authored-by: Furkan İ <developer@furkanipek.com.tr>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/tr/
Translation: Uptime Kuma/Uptime Kuma
2023-06-10 20:28:19 +00:00
Filip#0475
3707919025 Translated using Weblate (Slovak)
Currently translated at 28.1% (203 of 721 strings)

Co-authored-by: Filip#0475 <surinfilip@gmail.com>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/sk/
Translation: Uptime Kuma/Uptime Kuma
2023-06-10 20:28:19 +00:00
kokofixcomputers
d10e378fb1 Translated using Weblate (French)
Currently translated at 100.0% (721 of 721 strings)

Co-authored-by: kokofixcomputers <koko@trashmail.se>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/fr/
Translation: Uptime Kuma/Uptime Kuma
2023-06-10 20:28:18 +00:00
Alex Javadi
370328c3b8 Translated using Weblate (Persian)
Currently translated at 100.0% (746 of 746 strings)

Translated using Weblate (Persian)

Currently translated at 100.0% (722 of 722 strings)

Translated using Weblate (Persian)

Currently translated at 100.0% (721 of 721 strings)

Translated using Weblate (Persian)

Currently translated at 100.0% (721 of 721 strings)

Co-authored-by: Alex Javadi <15309978+aljvdi@users.noreply.github.com>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/fa/
Translation: Uptime Kuma/Uptime Kuma
2023-06-10 20:28:18 +00:00
Sergio Leon
1d8a82ae3e Translated using Weblate (Spanish)
Currently translated at 99.1% (715 of 721 strings)

Co-authored-by: Sergio Leon <serge@1nationgfx.com>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/es/
Translation: Uptime Kuma/Uptime Kuma
2023-06-10 20:28:18 +00:00
mickeydarrenlau
7da336e975 Translated using Weblate (Malay)
Currently translated at 3.6% (26 of 721 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (721 of 721 strings)

Added translation using Weblate (Malay)

Co-authored-by: mickeydarrenlau <darrenwjlau@gmail.com>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/ms/
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/zh_Hans/
Translation: Uptime Kuma/Uptime Kuma
2023-06-10 20:28:18 +00:00
@shiroo
b8c12cca2a Translated using Weblate (Arabic)
Currently translated at 94.3% (680 of 721 strings)

Co-authored-by: @shiroo <elrayan202021@gmail.com>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/ar/
Translation: Uptime Kuma/Uptime Kuma
2023-06-10 20:28:18 +00:00
Cyril59310
88fcfcc6fc Translated using Weblate (French)
Currently translated at 100.0% (721 of 721 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
2023-06-10 20:28:18 +00:00
Petros Giannhs
fcb22f7d05 Translated using Weblate (Greek)
Currently translated at 92.3% (666 of 721 strings)

Co-authored-by: Petros Giannhs <souvlaki420@gmail.com>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/el/
Translation: Uptime Kuma/Uptime Kuma
2023-06-10 20:28:18 +00:00
Windless
5be41990bc Translated using Weblate (Chinese (Traditional))
Currently translated at 92.4% (665 of 719 strings)

Co-authored-by: Windless <eason2008212@gmail.com>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/zh_Hant/
Translation: Uptime Kuma/Uptime Kuma
2023-06-10 20:28:18 +00:00
andershh
09149f50e0 Translated using Weblate (Danish)
Currently translated at 78.1% (562 of 719 strings)

Co-authored-by: andershh <ahh@jlbr.dk>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/da/
Translation: Uptime Kuma/Uptime Kuma
2023-06-10 20:28:18 +00:00
Buchtič
8f60274582 Translated using Weblate (Czech)
Currently translated at 100.0% (721 of 721 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (719 of 719 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (719 of 719 strings)

Co-authored-by: Buchtič <martin.buchta@gmail.com>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/cs/
Translation: Uptime Kuma/Uptime Kuma
2023-06-10 20:28:18 +00:00
Tomasz Ad
7dadac3ebe Translated using Weblate (Polish)
Currently translated at 100.0% (719 of 719 strings)

Co-authored-by: Tomasz Ad <djtms84@gmail.com>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/pl/
Translation: Uptime Kuma/Uptime Kuma
2023-06-10 20:28:18 +00:00
Hossein Niyumard
28c29f755d Translated using Weblate (Persian)
Currently translated at 100.0% (719 of 719 strings)

Co-authored-by: Hossein Niyumard <niyumard@riseup.net>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/fa/
Translation: Uptime Kuma/Uptime Kuma
2023-06-10 20:28:18 +00:00
Yoswaris Lawpaiboon
b426840b5b Translated using Weblate (Thai)
Currently translated at 86.4% (623 of 721 strings)

Translated using Weblate (Thai)

Currently translated at 85.8% (617 of 719 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
2023-06-10 20:28:18 +00:00
Lance
4f2d39d5fc Translated using Weblate (Chinese (Traditional))
Currently translated at 92.3% (664 of 719 strings)

Translated using Weblate (Chinese (Traditional))

Currently translated at 92.2% (663 of 719 strings)

Co-authored-by: Lance <2124757129@qq.com>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/zh_Hant/
Translation: Uptime Kuma/Uptime Kuma
2023-06-10 20:28:17 +00:00
AnnAngela
4012fc6964 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (721 of 721 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (719 of 719 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
2023-06-10 20:28:17 +00:00
Ömer Faruk Genç
d1b52bc098 Translated using Weblate (Turkish)
Currently translated at 100.0% (746 of 746 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (722 of 722 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (721 of 721 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (719 of 719 strings)

Co-authored-by: Ömer Faruk Genç <omer@farukgenc.com>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/tr/
Translation: Uptime Kuma/Uptime Kuma
2023-06-10 20:28:17 +00:00
Michal
c9a32f9dbb Translated using Weblate (Czech)
Currently translated at 99.5% (743 of 746 strings)

Translated using Weblate (Czech)

Currently translated at 98.9% (738 of 746 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (722 of 722 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (722 of 722 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (721 of 721 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (719 of 719 strings)

Co-authored-by: Michal <black23@gmail.com>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/cs/
Translation: Uptime Kuma/Uptime Kuma
2023-06-10 20:28:17 +00:00
stanol
b884f82de6 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (746 of 746 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (722 of 722 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (721 of 721 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (719 of 719 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (719 of 719 strings)

Co-authored-by: stanol <stanol777@gmail.com>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/uk/
Translation: Uptime Kuma/Uptime Kuma
2023-06-10 20:28:17 +00:00
Thiago Felipe Cruz E Souza
65928a26c7 Translated using Weblate (Portuguese (Brazil))
Currently translated at 75.6% (544 of 719 strings)

Co-authored-by: Thiago Felipe Cruz E Souza <thiago.felipe@tutanota.com>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/pt_BR/
Translation: Uptime Kuma/Uptime Kuma
2023-06-10 20:28:17 +00:00
DoyunShin
abe00efa7f Translated using Weblate (Korean)
Currently translated at 100.0% (719 of 719 strings)

Co-authored-by: DoyunShin <doyun.shin@gmail.com>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/ko/
Translation: Uptime Kuma/Uptime Kuma
2023-06-10 20:28:17 +00:00
Unai Tolosa Pontesta
0c364fc288 Translated using Weblate (Basque)
Currently translated at 75.7% (545 of 719 strings)

Co-authored-by: Unai Tolosa Pontesta <utolosa002@gmail.com>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/eu/
Translation: Uptime Kuma/Uptime Kuma
2023-06-10 20:28:17 +00:00
MrEddX
0d21529037 Translated using Weblate (Bulgarian)
Currently translated at 100.0% (746 of 746 strings)

Translated using Weblate (Bulgarian)

Currently translated at 100.0% (722 of 722 strings)

Translated using Weblate (Bulgarian)

Currently translated at 100.0% (721 of 721 strings)

Translated using Weblate (Bulgarian)

Currently translated at 100.0% (719 of 719 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
2023-06-10 20:28:17 +00:00
Fabian Beuke
37ae8eb44a Fix: prometheus monitor_status metric has 4 values
The prometheus monitor_status metric has actually 4 values. This can easily be verified by looking up the related source code or by using the metric in grafana an see values like 2 (which indicates timeout).
2023-06-10 20:22:33 +02:00
Louis Lam
fea8ef8367 Update bug_report.yaml 2023-06-09 20:50:13 +08:00
Louis Lam
37031fb9a7 Merge pull request #3222 from chakflying/fix/mysql-aborted-connection
Fix: Try to close mysql connection properly
2023-06-08 00:26:18 +08:00
Nelson Chan
58ec53fb1d Fix: Try to close mysql connection properly 2023-06-06 20:28:51 +08:00
Peace
57190b58c6 fix: dont show ping on details page of groups 2023-06-03 20:55:24 +02:00
Peace
6c2948d2de fix: list collapse storage 2023-06-03 20:54:52 +02:00
Louis Lam
68f389868c Update bug_report.yaml 2023-06-03 16:25:49 +08:00
Louis Lam
af5d7cbb0b Update README.md 2023-06-03 16:22:51 +08:00
Peace
56f448bfe5 fix: maintenance heredity 2023-05-31 21:29:20 +02:00
Peace
2b46da0f47 style: fix linting 2023-05-31 21:19:46 +02:00
Peace
9bd76c2795 Merge branch 'master' into group-monitors 2023-05-31 20:51:33 +02:00
Louis Lam
4b3a2ee71b Merge pull request #3211 from kiznick/badge-generator
Add i18n variable for Badge Generator #2915
2023-05-31 18:00:13 +08:00
Yoswaris Lawpaiboon
1634df5a39 Update en.json 2023-05-31 16:00:38 +07:00
Louis Lam
039fdb0730 Merge pull request #2915 from kiznick/badge-generator
Feat: Badge Generator
2023-05-31 16:09:13 +08:00
Louis Lam
20af2d9d95 Merge pull request #3209 from chakflying/fix/apikey-modal-layout
Fix: Fix incorrect modal layout in generate api-key
2023-05-31 15:09:58 +08:00
Nelson Chan
04806ba4f3 Fix: Fix incorrect modal layout 2023-05-31 09:26:54 +08:00
Yoswaris Lawpaiboon
3ff910a8f8 Fix Modal 2023-05-30 20:06:53 +07:00
Louis Lam
343a1d3344 Merge pull request #3203 from CommanderStorm/applied_timezone_formatting
chore: Made sure that every notification provider uses `timezone`/`localTime`
2023-05-30 20:36:32 +08:00
Louis Lam
f1c184c30c Update README.md 2023-05-30 17:37:53 +08:00
Frank Elsinga
f3c09f2bbd made every Notification provider supply time like dingding after #3152 2023-05-29 19:24:40 +02:00
Yoswaris Lawpaiboon
85eb084305 Setting Modal 2023-05-29 20:11:06 +07:00
Louis Lam
0735f12d19 Merge pull request #2594 from skaempfe/skaempfe#2593
Improvement: Support TLS Expiry alerts also for CA certs in cert chain
2023-05-28 22:13:48 +08:00
Louis Lam
8ed2b59410 Resolve conflict 2023-05-26 21:38:51 +08:00
Louis Lam
0b8dddba24 Merge remote-tracking branch 'origin/master' into skaempfe#2593
# Conflicts:
#	server/model/monitor.js
#	src/pages/Details.vue
2023-05-26 21:32:58 +08:00
Louis Lam
2114295381 Merge pull request #3052 from chakflying/ui/monitor-page-design-mobile
UI: Improve monitor page layout on mobile
2023-05-26 18:30:43 +08:00
Louis Lam
bc95875aa0 Merge pull request #3156 from maximilian-krauss/feat/add-pushover-ttl
feat: Adds message ttl to pushover notification
2023-05-26 18:18:24 +08:00
Louis Lam
c1efe0f26d Add a warning for Node.js >= 20 2023-05-26 18:09:05 +08:00
Maximilian Krauß
a0d0d5b015 fix: sends pushover ttl only if defined 2023-05-26 07:27:43 +02:00
Maximilian Krauß
8d05d80a5f feat: Adds message ttl to pushover notification 2023-05-26 07:27:43 +02:00
Louis Lam
36942de329 Merge pull request #2916 from lukasbableck/master
Add safe-area-inset-bottom padding to bottom-nav
2023-05-25 17:53:44 +08:00
Louis Lam
771ca09331 npm update (mainly for socket.io) 2023-05-25 13:41:35 +08:00
Louis Lam
3cb287a40e Merge pull request #3009 from chakflying/ui/url-more-monitor-types
UI: Support more monitor types in URL field
2023-05-24 20:46:48 +08:00
Nelson Chan
83a59bd984 Fix: Add password filtering 2023-05-22 04:17:45 +08:00
Nelson Chan
446b5fa9e4 UI: Support more monitor types in URL field 2023-05-21 16:03:05 +08:00
Zaid-maker
0d1b5321ad 🚀 Update legacy deps 2023-05-21 12:02:02 +08:00
Louis Lam
1e1cc86a10 Update Apprise to 1.4.0 2023-05-20 01:46:07 +08:00
Louis Lam
9dc02bb8e2 Update .gitignore 2023-05-16 21:57:08 +08:00
Louis Lam
bb15fa0179 Merge pull request #3154 from chakflying/fix/clear-data-remove-worker-thread
Fix: Remove use of worker threads in clear-old-data
2023-05-16 19:51:20 +08:00
Yoswaris Lawpaiboon
966066b897 Update src/components/BadgeGeneratorDialog.vue
Co-authored-by: Frank Elsinga <frank@elsinga.de>
2023-05-15 13:56:59 +07:00
Louis Lam
8d24891b8e Merge pull request #3054 from TechWilk/keyword-not-found-whitespace
Trim before truncating "keword not found" message
2023-05-13 18:36:04 +08:00
Louis Lam
ba7de3fd37 Merge pull request #3152 from AnnAngela/patch-1
feat: show time as server timezone in dingding notification
2023-05-13 18:15:05 +08:00
Nelson Chan
80c8fd7372 Chore: Remove util-worker 2023-05-13 01:51:23 +08:00
Nelson Chan
a27386bb92 Fix: Use croner for clear-old-data 2023-05-13 00:59:58 +08:00
AnnAngela
ce70b3fc62 feat: add a space to separate the words 2023-05-12 22:14:59 +08:00
AnnAngela
06fba5b55a feat: show time as server timezone in dingding notification 2023-05-12 22:04:44 +08:00
Louis Lam
f2c294e9e5 Merge pull request #3150 from theitguycj/master
Update README.md
2023-05-12 13:56:03 +08:00
The IT Guy CJ
332e54937e Update README.md 2023-05-11 23:19:09 -05:00
Louis Lam
a1adc30a89 Fix: Add back PagerTree 2023-05-11 14:56:42 +08:00
Louis Lam
6ce882ad4a Update README.md 2023-05-10 22:51:44 +08:00
Louis Lam
e392d12585 Mention in the README that Node.js 20 is not supported due to a weird issue 2023-05-09 23:37:51 +08:00
Louis Lam
253214ad2b Merge pull request #3024 from chakflying/feat/edit-tag-multiselect
UI: Use vue-multiselect in Edit Tag & Styling Fixes
2023-05-09 20:43:30 +08:00
Louis Lam
33de7bdb1c Merge conflict 2023-05-09 00:45:31 +08:00
Louis Lam
7f5d0e5490 Merge remote-tracking branch 'origin/1.21.X'
# Conflicts:
#	package-lock.json
2023-05-09 00:42:11 +08:00
Louis Lam
1a344c1371 Update to 1.21.3 2023-05-09 00:28:29 +08:00
Louis Lam
28b0f8fc00 Update dependencies 2023-05-08 22:52:57 +08:00
Louis Lam
0eaaa8b6fa Minor 2023-05-08 22:52:41 +08:00
Louis Lam
5cd506e340 Minor 2023-05-08 22:39:32 +08:00
Louis Lam
f0beccf6bf Fix Same As Server Timezone do not save correctly 2023-05-08 22:14:58 +08:00
Louis Lam
72c16c3aa2 Fix eslint warnings 2023-05-08 04:26:11 +08:00
Louis Lam
aa8454b73f Slightly improve error check on maintenance edit page 2023-05-08 04:14:24 +08:00
Louis Lam
d23cb0b382 Fix maintenance do not start after 1.21.2 2023-05-08 04:08:30 +08:00
Nelson Chan
9975050872 Chore: Fix line break 2023-05-07 23:20:28 +08:00
Nelson Chan
f8c2909576 UI: Improve styling 2023-05-07 23:20:28 +08:00
Nelson Chan
fcfe13e52d Feat: Use vue-multiselect in Edit Tag 2023-05-07 23:20:28 +08:00
Nelson Chan
9f51115a19 Chore: Fix lint 2023-05-07 23:20:03 +08:00
Nelson Chan
4057ca6e72 UI: Improve monitor page on mobile 2023-05-07 23:20:03 +08:00
Louis Lam
8a3bce44ef Update dependenices 2023-05-02 16:22:00 +08:00
Louis Lam
dfe6f52f6a Add test for Node.js 20, drop 19 2023-05-02 16:17:37 +08:00
Louis Lam
333a631389 Merge pull request #3106 from shihaamabr/master
Fix typo in dashboard TCP Port items
2023-04-29 12:44:31 +08:00
Shihaam Abdul Rahman
eaa948579b Fix typo in dashboard TCP Port times 2023-04-27 12:34:01 +05:00
Louis Lam
74dd07c3ca Merge pull request #3101 from stumpylog/feature/cloudflare-pkgs
Install cloudflared via Cloudflare Package Repository
2023-04-25 21:10:25 +08:00
Louis Lam
f75cf3a186 Merge pull request #2905 from Sharknoon/ntfy-bearer-authorization
Added option for notification provider ntfy to use access tokens
2023-04-25 18:24:44 +08:00
Louis Lam
a3e31b22bc Minor 2023-04-25 18:22:17 +08:00
Louis Lam
078d1f96a5 Better handling for old added ntfy notifications 2023-04-25 18:17:32 +08:00
Louis Lam
8207f16396 Merge remote-tracking branch 'origin/master' into ntfy-bearer-authorization 2023-04-25 18:07:52 +08:00
Trenton Holmes
ba82abe5f3 Updates the install of cloudflared to utilize the Cloudflare Package Repository 2023-04-24 06:47:44 -07:00
Louis Lam
eb9c748071 Merge pull request #3006 from chakflying/ui/tags-settings-mobile
UI: Improve Tags settings design on mobile
2023-04-17 16:58:57 +08:00
Yoswaris Lawpaiboon
3579520575 Fix Eslint
Co-authored-by: Nelson Chan <3271800+chakflying@users.noreply.github.com>
2023-04-12 20:09:04 +07:00
Yoswaris Lawpaiboon
030faddd1c Merge branch 'louislam:master' into badge-generator 2023-04-12 12:02:36 +07:00
Christopher Wilkinson
0e516a42e5 Trim before truncating "keword not found" message 2023-04-11 16:57:30 +01:00
Louis Lam
680dccefea Merge pull request #2868 from chakflying/update-chartjs
Chore: Update chart.js & improve performance
2023-04-11 19:01:47 +08:00
Louis Lam
8c9423f4de Merge conflicts manually 2023-04-11 19:01:17 +08:00
Louis Lam
f433f33418 Merge remote-tracking branch 'origin/master' into update-chartjs
# Conflicts:
#	package-lock.json
#	package.json
2023-04-11 18:58:20 +08:00
Louis Lam
d4a31cf02a Merge pull request #2867 from Small6oy/grpc-patch
Resolved issue with using IP Address as GRPC URL
2023-04-11 18:55:43 +08:00
Louis Lam
a7588adc52 Merge branch 'master' into grpc-patch 2023-04-11 18:55:27 +08:00
Louis Lam
6356b1e50a Merge pull request #2961 from chakflying/feat/flush-wal
Chore: Flush WAL on shutdown
2023-04-11 18:53:58 +08:00
Louis Lam
af6e01ee3a Merge pull request #3010 from chakflying/fix/grpc-invalid-regex
Fix: Remove invalid gRPC url regex
2023-04-11 18:43:51 +08:00
Josua Frank
11f4cb8725 Merge branch 'louislam:master' into ntfy-bearer-authorization 2023-04-10 16:06:53 +02:00
Louis Lam
1bf97e701d Merge pull request #2837 from dsb3/hostname-regex
Feature: hostnames may be specified with a trailing dot
2023-04-09 16:27:16 +08:00
Louis Lam
4c1ac5e870 Merge pull request #2863 from mtelgkamp/ntfy-notification-improvements
Improve ntfy notifications
2023-04-09 16:08:12 +08:00
Louis Lam
9e320dc5fb Expose timezone and local datetime to notification providers 2023-04-09 16:01:27 +08:00
Louis Lam
2f3f929fbd Merge pull request #2831 from mtelgkamp/mattermost-notification-improvements
Improve mattermost notifications
2023-04-09 15:42:28 +08:00
Louis Lam
b776e88b26 Merge pull request #3042 from Zaid-maker/master
🐛 fix(package.json): correct typo in deploy-demo-server script name
2023-04-07 16:32:23 +08:00
Zaid-maker
49741bbef2 🐛 fix(package.json): correct typo in deploy-demo-server script name 2023-04-07 02:09:13 +05:00
Josua Frank
1f7f1f70bf Merge branch 'louislam:master' into ntfy-bearer-authorization 2023-04-06 10:55:28 +02:00
Louis Lam
be7d3f6142 Merge pull request #2752 from titanventura/show-tags-in-status-page-monitor-select-list
show tags in monitor select list under status page
2023-04-04 16:13:26 +08:00
Louis Lam
7706c29564 Minor 2023-04-04 15:42:37 +08:00
Louis Lam
9dd1b1ca0f Merge remote-tracking branch 'origin/master' into show-tags-in-status-page-monitor-select-list
# Conflicts:
#	src/pages/StatusPage.vue
2023-04-04 15:35:20 +08:00
Louis Lam
21ad715e6a Merge pull request #3021 from louislam/1.22.X
1.22.x -> master
2023-04-04 14:58:34 +08:00
Josua Frank
23af66f618 Merge branch 'louislam:master' into ntfy-bearer-authorization 2023-04-03 21:17:53 +02:00
Louis Lam
03aa685d3f Update to 1.21.2 2023-04-04 01:58:56 +08:00
Louis Lam
84c1baf706 Merge pull request #3015 from UptimeKumaBot/weblate-uptime-kuma-uptime-kuma
Translations Update from Weblate
2023-04-04 00:09:45 +08:00
Weblate
23808efe2a Merge remote-tracking branch 'origin/master' 2023-04-03 16:07:13 +00:00
Marco
1db25a329f Translated using Weblate (German)
Currently translated at 100.0% (719 of 719 strings)

Translated using Weblate (German (Switzerland))

Currently translated at 100.0% (719 of 719 strings)

Co-authored-by: Marco <marco@nanoweb.ch>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/de/
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/de_CH/
Translation: Uptime Kuma/Uptime Kuma
2023-04-03 16:06:58 +00:00
Ömer Faruk Genç
e314d517ad Translated using Weblate (Turkish)
Currently translated at 100.0% (719 of 719 strings)

Co-authored-by: Ömer Faruk Genç <omer@farukgenc.com>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/tr/
Translation: Uptime Kuma/Uptime Kuma
2023-04-03 16:06:58 +00:00
__filename
84d1cb73b6 Translated using Weblate (Korean)
Currently translated at 99.7% (717 of 719 strings)

Co-authored-by: __filename <filename@inft.kr>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/ko/
Translation: Uptime Kuma/Uptime Kuma
2023-04-03 16:06:58 +00:00
Buchtič
ddd3d3bc92 Translated using Weblate (Czech)
Currently translated at 100.0% (719 of 719 strings)

Co-authored-by: Buchtič <martin.buchta@gmail.com>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/cs/
Translation: Uptime Kuma/Uptime Kuma
2023-04-03 16:06:58 +00:00
Alex Javadi
190e85d2c8 Translated using Weblate (Persian)
Currently translated at 100.0% (719 of 719 strings)

Co-authored-by: Alex Javadi <15309978+aljvdi@users.noreply.github.com>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/fa/
Translation: Uptime Kuma/Uptime Kuma
2023-04-03 16:06:58 +00:00
MaxGremory
d8511fa201 Translated using Weblate (Spanish)
Currently translated at 99.0% (712 of 719 strings)

Co-authored-by: MaxGremory <holgaloper@gmail.com>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/es/
Translation: Uptime Kuma/Uptime Kuma
2023-04-03 16:06:58 +00:00
rubesaca
e76d29dee5 Translated using Weblate (Spanish)
Currently translated at 99.0% (712 of 719 strings)

Co-authored-by: rubesaca <rubesaca@gmail.com>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/es/
Translation: Uptime Kuma/Uptime Kuma
2023-04-03 16:06:58 +00:00
MaxGremory
fb38048159 Translated using Weblate (Spanish)
Currently translated at 99.0% (712 of 719 strings)

Co-authored-by: MaxGremory <holgaloper@gmail.com>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/es/
Translation: Uptime Kuma/Uptime Kuma
2023-04-03 16:06:57 +00:00
Louis Lam
80f1959871 Translated using Weblate (Chinese (Traditional, Hong Kong))
Currently translated at 96.1% (691 of 719 strings)

Co-authored-by: Louis Lam <louislam@users.noreply.github.com>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/zh_Hant_HK/
Translation: Uptime Kuma/Uptime Kuma
2023-04-03 16:06:57 +00:00
Ademaro
4ddc3b5f5e Translated using Weblate (Russian)
Currently translated at 100.0% (719 of 719 strings)

Co-authored-by: Ademaro <ademaro@ya.ru>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/ru/
Translation: Uptime Kuma/Uptime Kuma
2023-04-03 16:06:57 +00:00
Cyril59310
d173a3c663 Translated using Weblate (French)
Currently translated at 100.0% (719 of 719 strings)

Translated using Weblate (French)

Currently translated at 99.8% (718 of 719 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
2023-04-03 16:06:57 +00:00
Louis Lam
45ef7b2f69 Fix Effective Date Range cannot be removed 2023-04-03 21:01:58 +08:00
Josua Frank
6b078b83bd Merge branch 'master' into ntfy-bearer-authorization 2023-04-03 08:33:05 +02:00
Louis Lam
22f730499f Improve the database connection string input 2023-04-03 02:57:14 +08:00
Louis Lam
1be74e2720 Merge pull request #2870 from chakflying/feat/auto-theme-status-page
Feat: Support auto theme in status pages
2023-04-03 02:38:30 +08:00
Louis Lam
32f84b5e4e Merge pull request #2491 from RubenNL/fix-metrics-push
Fixed the metrics for the push type.
2023-04-02 02:05:03 +08:00
Nelson Chan
97c7ad9cc7 Fix: Remove invalid gRPC url regex 2023-04-02 00:07:07 +08:00
Louis Lam
8f449ab738 Update to 1.21.2-beta.0 2023-04-01 16:36:51 +08:00
Louis Lam
dbfaddafca Validate cron before submit 2023-04-01 16:30:55 +08:00
Louis Lam
511038b45a Remove unused code 2023-04-01 04:22:10 +08:00
Louis Lam
e8d48561fc Change Retries from 0 to 1 2023-04-01 01:45:16 +08:00
Louis Lam
aeb2feacd3 Merge remote-tracking branch 'weblate/master'
# Conflicts:
#	src/lang/fr-FR.json
#	src/lang/tr-TR.json
2023-04-01 01:30:04 +08:00
DevMirza
c01055efb3 Translated using Weblate (Urdu)
Currently translated at 62.4% (445 of 713 strings)

Translation: Uptime Kuma/Uptime Kuma
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/ur/
2023-03-31 17:27:08 +00:00
Louis Lam
17ae47d091 Drop database backup logic, because duplicating a relative large database likely causes a disk space issue, users should take backup manually instead. 2023-04-01 00:58:23 +08:00
Louis Lam
de0d1edfd4 Merge pull request #2988 from chakflying/fix/socks-rejectUnauthorized
Fix: Pass rejectUnauthorized to Socks Proxy
2023-04-01 00:48:28 +08:00
Louis Lam
0f5a243450 Merge pull request #3003 from louislam/maintenance-fix
Maintenance Core Bug Fix & Improvements
2023-04-01 00:36:15 +08:00
Nelson Chan
b975c24531 UI: Improve design on mobile 2023-03-31 21:54:35 +08:00
Louis Lam
524cf7c607 WIP 2023-03-31 21:34:05 +08:00
Louis Lam
a6acd065bb WIP 2023-03-31 20:58:45 +08:00
Louis Lam
227cec86a8 WIP 2023-03-31 20:25:37 +08:00
Josua Frank
ba52e1c885 Merge branch 'louislam:master' into ntfy-bearer-authorization 2023-03-31 11:31:13 +02:00
Louis Lam
02291730fe WIP 2023-03-31 04:04:17 +08:00
Louis Lam
dcc065c86f Merge pull request #3001 from GrantBirki/master
:fishsticks: Upgrade json-yaml-validate
2023-03-30 20:33:58 +08:00
Grant Birkinbine
501dc29e6d use exclude file 2023-03-30 11:46:28 +01:00
Marco
d2b09ef042 Translated using Weblate (German)
Currently translated at 100.0% (713 of 713 strings)

Translated using Weblate (German (Switzerland))

Currently translated at 100.0% (713 of 713 strings)

Co-authored-by: Marco <marco@nanoweb.ch>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/de/
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/de_CH/
Translation: Uptime Kuma/Uptime Kuma
2023-03-28 18:55:27 +00:00
Ömer Faruk Genç
f608590526 Translated using Weblate (Turkish)
Currently translated at 100.0% (713 of 713 strings)

Co-authored-by: Ömer Faruk Genç <omer@farukgenc.com>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/tr/
Translation: Uptime Kuma/Uptime Kuma
2023-03-28 18:55:27 +00:00
Adam Stachowicz
a7f21bffec Translated using Weblate (Polish)
Currently translated at 100.0% (713 of 713 strings)

Co-authored-by: Adam Stachowicz <saibamenppl@gmail.com>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/pl/
Translation: Uptime Kuma/Uptime Kuma
2023-03-28 18:55:27 +00:00
Alex Javadi
ebd42444d1 Translated using Weblate (Persian)
Currently translated at 100.0% (713 of 713 strings)

Translated using Weblate (Persian)

Currently translated at 100.0% (713 of 713 strings)

Co-authored-by: Alex Javadi <15309978+aljvdi@users.noreply.github.com>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/fa/
Translation: Uptime Kuma/Uptime Kuma
2023-03-28 18:55:27 +00:00
Mirinek
f0f7645c57 Translated using Weblate (Czech)
Currently translated at 100.0% (709 of 709 strings)

Co-authored-by: Mirinek <mirek.nozicka77@gmail.com>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/cs/
Translation: Uptime Kuma/Uptime Kuma
2023-03-28 18:55:27 +00:00
Louis Lam
3ada6fa99b Translated using Weblate (Chinese (Traditional, Hong Kong))
Currently translated at 96.0% (681 of 709 strings)

Co-authored-by: Louis Lam <louislam@users.noreply.github.com>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/zh_Hant_HK/
Translation: Uptime Kuma/Uptime Kuma
2023-03-28 18:55:27 +00:00
401Unauthorized
4fb10a4e3f Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (709 of 709 strings)

Co-authored-by: 401Unauthorized <hi@4o1.to>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/zh_Hans/
Translation: Uptime Kuma/Uptime Kuma
2023-03-28 18:55:27 +00:00
Taha İPEK
1aac15bccc Translated using Weblate (Turkish)
Currently translated at 100.0% (709 of 709 strings)

Co-authored-by: Taha İPEK <ti.tahaipek@gmail.com>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/tr/
Translation: Uptime Kuma/Uptime Kuma
2023-03-28 18:55:27 +00:00
Alex
cbcd2a1027 Translated using Weblate (Russian)
Currently translated at 99.8% (708 of 709 strings)

Co-authored-by: Alex <proactivecam@inbox.ru>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/ru/
Translation: Uptime Kuma/Uptime Kuma
2023-03-28 18:55:27 +00:00
Cyril59310
17f038767d Translated using Weblate (French)
Currently translated at 100.0% (713 of 713 strings)

Translated using Weblate (French)

Currently translated at 100.0% (709 of 709 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
2023-03-28 18:55:26 +00:00
Peter Petrík
edd1c6b662 Translated using Weblate (Slovak)
Currently translated at 27.2% (193 of 708 strings)

Co-authored-by: Peter Petrík <peter.petrik.fefe@gmail.com>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/sk/
Translation: Uptime Kuma/Uptime Kuma
2023-03-28 18:55:26 +00:00
ilya12077
29be8d3ddb Translated using Weblate (Russian)
Currently translated at 100.0% (708 of 708 strings)

Co-authored-by: ilya12077 <mr.ilya.1207@gmail.com>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/ru/
Translation: Uptime Kuma/Uptime Kuma
2023-03-28 18:55:26 +00:00
Alex
2ac1abd424 Translated using Weblate (Russian)
Currently translated at 100.0% (708 of 708 strings)

Co-authored-by: Alex <proactivecam@inbox.ru>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/ru/
Translation: Uptime Kuma/Uptime Kuma
2023-03-28 18:55:26 +00:00
DoyunShin
738a494dcb Translated using Weblate (Korean)
Currently translated at 97.7% (692 of 708 strings)

Co-authored-by: DoyunShin <doyun.shin@gmail.com>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/ko/
Translation: Uptime Kuma/Uptime Kuma
2023-03-28 18:55:26 +00:00
AmadeusGraves
e575d41f7d Translated using Weblate (Spanish)
Currently translated at 99.2% (703 of 708 strings)

Co-authored-by: AmadeusGraves <angelfx19@gmail.com>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/es/
Translation: Uptime Kuma/Uptime Kuma
2023-03-28 18:55:26 +00:00
Nelson Chan
8ee4b844fd Fix: Pass rejectUnauthorized to Socks Proxy 2023-03-28 11:40:19 +08:00
Louis Lam
4ae437dd61 Update to 1.21.1 2023-03-27 20:09:24 +08:00
Louis Lam
6cb296b07a Update SQLite and Vue 2023-03-27 19:05:18 +08:00
Louis Lam
644c6a872f Merge pull request #2978 from UptimeKumaBot/weblate-uptime-kuma-uptime-kuma
Translations Update from Weblate
2023-03-27 17:54:50 +08:00
Weblate
8c69c18f6d Merge remote-tracking branch 'origin/master' 2023-03-27 08:38:00 +00:00
Mirinek
c1a1160767 Translated using Weblate (Czech)
Currently translated at 100.0% (709 of 709 strings)

Co-authored-by: Mirinek <mirek.nozicka77@gmail.com>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/cs/
Translation: Uptime Kuma/Uptime Kuma
2023-03-27 08:37:51 +00:00
Louis Lam
a0ebd88849 Translated using Weblate (Chinese (Traditional, Hong Kong))
Currently translated at 96.0% (681 of 709 strings)

Co-authored-by: Louis Lam <louislam@users.noreply.github.com>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/zh_Hant_HK/
Translation: Uptime Kuma/Uptime Kuma
2023-03-27 08:37:51 +00:00
401Unauthorized
2e9413cf33 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (709 of 709 strings)

Co-authored-by: 401Unauthorized <hi@4o1.to>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/zh_Hans/
Translation: Uptime Kuma/Uptime Kuma
2023-03-27 08:37:51 +00:00
Taha İPEK
278b52ec34 Translated using Weblate (Turkish)
Currently translated at 100.0% (709 of 709 strings)

Co-authored-by: Taha İPEK <ti.tahaipek@gmail.com>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/tr/
Translation: Uptime Kuma/Uptime Kuma
2023-03-27 08:37:51 +00:00
Alex
caa757a27c Translated using Weblate (Russian)
Currently translated at 99.8% (708 of 709 strings)

Co-authored-by: Alex <proactivecam@inbox.ru>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/ru/
Translation: Uptime Kuma/Uptime Kuma
2023-03-27 08:37:51 +00:00
Cyril59310
3ce117a943 Translated using Weblate (French)
Currently translated at 100.0% (709 of 709 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
2023-03-27 08:37:51 +00:00
Peter Petrík
cefe484b47 Translated using Weblate (Slovak)
Currently translated at 27.2% (193 of 708 strings)

Co-authored-by: Peter Petrík <peter.petrik.fefe@gmail.com>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/sk/
Translation: Uptime Kuma/Uptime Kuma
2023-03-27 08:37:51 +00:00
ilya12077
a700892709 Translated using Weblate (Russian)
Currently translated at 100.0% (708 of 708 strings)

Co-authored-by: ilya12077 <mr.ilya.1207@gmail.com>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/ru/
Translation: Uptime Kuma/Uptime Kuma
2023-03-27 08:37:50 +00:00
Alex
13d721ccf8 Translated using Weblate (Russian)
Currently translated at 100.0% (708 of 708 strings)

Co-authored-by: Alex <proactivecam@inbox.ru>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/ru/
Translation: Uptime Kuma/Uptime Kuma
2023-03-27 08:37:50 +00:00
DoyunShin
6c66bff518 Translated using Weblate (Korean)
Currently translated at 97.7% (692 of 708 strings)

Co-authored-by: DoyunShin <doyun.shin@gmail.com>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/ko/
Translation: Uptime Kuma/Uptime Kuma
2023-03-27 08:37:50 +00:00
AmadeusGraves
bea51d048b Translated using Weblate (Spanish)
Currently translated at 99.2% (703 of 708 strings)

Co-authored-by: AmadeusGraves <angelfx19@gmail.com>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/es/
Translation: Uptime Kuma/Uptime Kuma
2023-03-27 08:37:50 +00:00
Louis Lam
2e1a0fe4d5 Merge pull request #2983 from GrantBirki/master
GitHub Actions - Validate json / yaml files 📁
2023-03-27 16:36:20 +08:00
grantbirki
27b0895722 fix branch names 2023-03-26 22:35:00 +01:00
grantbirki
e687698851 add json / yaml validate job 2023-03-26 22:34:25 +01:00
Josua Frank
fc4312ca1a Merge branch 'master' into ntfy-bearer-authorization 2023-03-26 19:09:48 +02:00
Louis Lam
fbdeb30ce7 Merge pull request #2973 from chakflying/fix/limit-precision
Fix: Apply toPrecision as last step
2023-03-26 15:46:00 +08:00
Louis Lam
41bda4e1d7 Merge pull request #2975 from chakflying/fix/badge-no-label
Fix: Allow status badge with empty label
2023-03-26 15:42:12 +08:00
Louis Lam
4869e6531c Merge pull request #2980 from Genc/feature/twilio-notification-provider
Add Twilio Sms Notification Provider
2023-03-26 15:38:53 +08:00
Louis Lam
302b9cf644 Merge pull request #2956 from wwniclask25/feature/opsgenie-alerts
Feat: Add opsgenie notification provider
2023-03-26 15:36:50 +08:00
Louis Lam
3c3a192943 Merge pull request #2906 from chakflying/fix/duplicate-expiry-notif
Fix: Check for TLS expiry notified days smaller than target
2023-03-26 15:34:26 +08:00
Faruk Genç
b64c835cee Add Twilio Sms Notification Provider 2023-03-25 19:56:01 +03:00
Louis Lam
5266e713e6 Update ask-for-help.yaml 2023-03-25 23:06:51 +08:00
Louis Lam
86579d245f Update ask-for-help.yaml 2023-03-25 23:06:23 +08:00
Louis Lam
b6169408be Merge pull request #2962 from chakflying/fix/missing-clear-form
Fix: Add missing clearForm func.
2023-03-25 14:31:47 +08:00
Nelson Chan
4f05912276 Fix: Allow status badge with empty label 2023-03-25 02:44:15 +08:00
Nelson Chan
bf525371d9 Fix: Apply toPrecision as last step 2023-03-24 22:42:50 +08:00
Louis Lam
89bfc3bf33 Merge pull request #2908 from chakflying/chore/encrypted-private-key
Chore: Add support for encrypted SSL-key
2023-03-24 21:36:22 +08:00
Louis Lam
a2014278b8 Fix #2969 2023-03-24 19:16:12 +08:00
Louis Lam
70572af1af Fix #2969 2023-03-24 18:40:05 +08:00
Louis Lam
b31c23a43b Merge pull request #2970 from louislam/fix-2968
Fix crash issue caused by mysqlQuery()
2023-03-24 18:20:15 +08:00
Louis Lam
f4ee5271af Improve error handling of mysqlQuery and return row count as result 2023-03-24 16:24:00 +08:00
Louis Lam
7330db3563 Improve error handling of mysqlQuery and return row count as result 2023-03-24 16:08:30 +08:00
Louis Lam
097567e5f0 Merge pull request #2878 from chakflying/feat/status-page-countdown
Feat: Add status page countdown to refresh
2023-03-23 17:18:57 +08:00
Louis Lam
35f300c8eb Improve and reuse language keys 2023-03-23 17:17:53 +08:00
Nelson Chan
4c9d7ac8ca Fix: Add missing clearForm func. 2023-03-22 15:06:38 +08:00
Nelson Chan
ca52047bf5 Feat: Flush WAL on shutdown 2023-03-22 14:46:58 +08:00
niclas.koegl
d9558833fc Fix linting 2023-03-21 19:45:44 +01:00
niclas.koegl
776a482a1d Add Opsgenie notification provider 2023-03-21 19:29:37 +01:00
niclas.koegl
d2527d7254 Merge branch 'master' into feature/opsgenie-alerts 2023-03-21 18:10:10 +01:00
niclas.koegl
6dfca0c163 Add Opsgenie notification provider 2023-03-21 18:07:19 +01:00
Josua Frank
df47609671 Added default dropdown value 2023-03-21 13:55:51 +01:00
Josua Frank
e63f7562f8 linter fixes 2023-03-21 13:48:00 +01:00
Josua Frank
8921ed0cff fix indentation of language json files 2023-03-21 13:44:06 +01:00
Josua Frank
35a56dd9e0 Added dropdown for authentication methods 2023-03-21 13:40:24 +01:00
Josua Frank
442f54de84 Merge branch 'louislam:master' into ntfy-bearer-authorization 2023-03-21 13:01:49 +01:00
Louis Lam
72317633d9 Update to 1.21.0 2023-03-20 18:07:54 +08:00
Louis Lam
1973db28bf minor 2023-03-20 18:02:31 +08:00
Louis Lam
972ae60cfc Merge pull request #2913 from UptimeKumaBot/weblate-uptime-kuma-uptime-kuma
Translations Update from Weblate
2023-03-20 18:01:42 +08:00
Weblate
56410afc3b Merge remote-tracking branch 'origin/master' 2023-03-20 09:57:54 +00:00
Alanimdeo
df975c2750 Translated using Weblate (Korean)
Currently translated at 97.1% (688 of 708 strings)

Co-authored-by: Alanimdeo <alan@imdeo.kr>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/ko/
Translation: Uptime Kuma/Uptime Kuma
2023-03-20 09:57:49 +00:00
Louis Lam
0cfd3fa642 Translated using Weblate (Georgian)
Currently translated at 1.5% (11 of 708 strings)

Co-authored-by: Louis Lam <louislam@users.noreply.github.com>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/ka/
Translation: Uptime Kuma/Uptime Kuma
2023-03-20 09:57:49 +00:00
Yoswaris Lawpaiboon
0b91391ced Translated using Weblate (Thai)
Currently translated at 81.3% (576 of 708 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
2023-03-20 09:57:49 +00:00
Bart Callant
02fde56aec Translated using Weblate (Dutch)
Currently translated at 94.7% (671 of 708 strings)

Co-authored-by: Bart Callant <bart@callant.net>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/nl/
Translation: Uptime Kuma/Uptime Kuma
2023-03-20 09:57:49 +00:00
Mikolajek
da225a225f Translated using Weblate (French)
Currently translated at 100.0% (708 of 708 strings)

Co-authored-by: Mikolajek <weblate-1530@npk.pm>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/fr/
Translation: Uptime Kuma/Uptime Kuma
2023-03-20 09:57:49 +00:00
Marco
a40c03f6f3 Translated using Weblate (German)
Currently translated at 100.0% (708 of 708 strings)

Translated using Weblate (German (Switzerland))

Currently translated at 100.0% (708 of 708 strings)

Co-authored-by: Marco <marco@nanoweb.ch>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/de/
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/de_CH/
Translation: Uptime Kuma/Uptime Kuma
2023-03-20 09:57:49 +00:00
Ghvinerias
c238508060 Translated using Weblate (Georgian)
Currently translated at 1.6% (12 of 708 strings)

Added translation using Weblate (Georgian)

Co-authored-by: Ghvinerias <Ghvinerias@gmail.com>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/ka/
Translation: Uptime Kuma/Uptime Kuma
2023-03-20 09:57:49 +00:00
Tomasz Bielinski
abcbc3c55e Translated using Weblate (Slovak)
Currently translated at 11.7% (83 of 708 strings)

Added translation using Weblate (Slovak)

Co-authored-by: Tomasz Bielinski <tomasz@bielinski.sk>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/sk/
Translation: Uptime Kuma/Uptime Kuma
2023-03-20 09:57:49 +00:00
DoyunShin
350451edc8 Translated using Weblate (Korean)
Currently translated at 95.6% (677 of 708 strings)

Translated using Weblate (Korean)

Currently translated at 95.0% (673 of 708 strings)

Co-authored-by: DoyunShin <doyun.shin@gmail.com>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/ko/
Translation: Uptime Kuma/Uptime Kuma
2023-03-20 09:57:49 +00:00
Nelson Chan
b4b2ae55c0 Translated using Weblate (Chinese (Traditional, Hong Kong))
Currently translated at 95.6% (677 of 708 strings)

Co-authored-by: Nelson Chan <chakflying@hotmail.com>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/zh_Hant_HK/
Translation: Uptime Kuma/Uptime Kuma
2023-03-20 09:57:48 +00:00
stanol
9ecb890f56 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (708 of 708 strings)

Co-authored-by: stanol <stanol777@gmail.com>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/uk/
Translation: Uptime Kuma/Uptime Kuma
2023-03-20 09:57:48 +00:00
Ömer Faruk Genç
4329fc6751 Translated using Weblate (Turkish)
Currently translated at 100.0% (708 of 708 strings)

Co-authored-by: Ömer Faruk Genç <omer@farukgenc.com>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/tr/
Translation: Uptime Kuma/Uptime Kuma
2023-03-20 09:57:48 +00:00
Alexey
836e125256 Translated using Weblate (Russian)
Currently translated at 92.9% (658 of 708 strings)

Co-authored-by: Alexey <aosmirnov@gmail.com>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/ru/
Translation: Uptime Kuma/Uptime Kuma
2023-03-20 09:57:48 +00:00
Donker_Jumala
0fe98de256 Translated using Weblate (Japanese)
Currently translated at 72.1% (511 of 708 strings)

Co-authored-by: Donker_Jumala <weareh0711@gmail.com>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/ja/
Translation: Uptime Kuma/Uptime Kuma
2023-03-20 09:57:48 +00:00
Deathart
3825dd9f42 Translated using Weblate (French)
Currently translated at 100.0% (708 of 708 strings)

Co-authored-by: Deathart <deathart@hotmail.fr>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/fr/
Translation: Uptime Kuma/Uptime Kuma
2023-03-20 09:57:48 +00:00
Cyril59310
c6cd0d9312 Translated using Weblate (French)
Currently translated at 100.0% (708 of 708 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
2023-03-20 09:57:48 +00:00
Michal
82975f8d7b Translated using Weblate (Czech)
Currently translated at 100.0% (708 of 708 strings)

Co-authored-by: Michal <black23@gmail.com>
Translate-URL: https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/cs/
Translation: Uptime Kuma/Uptime Kuma
2023-03-20 09:57:48 +00:00
MrEddX
2ebbcc25a9 Translated using Weblate (Bulgarian)
Currently translated at 100.0% (708 of 708 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
2023-03-20 09:57:48 +00:00
Louis Lam
f2323b012b Deleted translation using Weblate (Chinese (Literary))
Deleted translation using Weblate (Mongolian)

Co-authored-by: Louis Lam <louislam@users.noreply.github.com>
2023-03-20 09:57:48 +00:00
401Unauthorized
e5a6238cde chore: remove invalid template 2023-03-19 21:59:34 +08:00
Louis Lam
61506b1af2 [Deploy to demo] No need to restart demo-kuma 2023-03-15 15:06:53 +08:00
tombii
dbe73bd6ae Update monitor.js (#2929)
Language
2023-03-15 15:00:28 +08:00
Ghvinerias
0778549a6d Update i18n.js (#2927)
I want to commit to this project by translating it to Georgian,
I added "ge" : "ქართული", "ქართული" means Georgian in Georgian language
2023-03-15 14:57:47 +08:00
Louis Lam
09fa60de55 Merge pull request #2531 from doubles-ss/master
Feat: Add mtls authen option to http
2023-03-14 00:04:40 +08:00
Louis Lam
1e80365b73 Update node-ping to 0.4.4 2023-03-12 20:44:30 +08:00
Louis Lam
491239415e Merge remote-tracking branch 'origin/master' into doubles-ss_master
# Conflicts:
#	server/database.js
2023-03-12 18:38:19 +08:00
Lukas Bableck
cf59832d51 Set viewport-fit=cover in meta viewport tag 2023-03-10 17:42:30 +01:00
Lukas Bableck
8f259e1756 Add padding to bottom-nav 2023-03-10 17:42:06 +01:00
Yoswaris Lawpaiboon
29b2809279 Change Icon, Add missing var 2023-03-10 22:04:47 +07:00
Yoswaris Lawpaiboon
16f2701f61 idk how to fix camelcase lint 😢 2023-03-10 20:24:10 +07:00
Yoswaris Lawpaiboon
3bbf269da0 generator modal 2023-03-10 19:25:04 +07:00
Yoswaris Lawpaiboon
56d716cee4 Create badge-list.md 2023-03-10 17:46:45 +07:00
Nelson Chan
391692a708 Chore: Add support for encrypted SSL-key 2023-03-09 00:00:07 +08:00
Nelson Chan
f32fcb204f Fix: Check for notified days smaller than target 2023-03-08 22:26:19 +08:00
Josua Frank
e8814e8479 added option for ntfy access tokens 2023-03-08 13:28:02 +00:00
Nelson Chan
193a273557 Feat: Add status page countdown to refresh 2023-03-03 08:25:41 +08:00
Nelson Chan
150607cc93 Feat: Support auto theme in status pages 2023-03-02 07:26:26 +08:00
Michael Telgkamp
cbbd3e20ad Codestyle: Add trailing comma 2023-03-01 23:05:23 +01:00
Nelson Chan
beb22f743d Chore: Update chart.js & improve perf. 2023-03-02 04:47:51 +08:00
Godwin Gabriel Ndlovu
6fc34e44d9 Resolved issue with using IP Address as GRPC URL
I've been having an issue with trying to use an IPAddress Host:Port combination in monitoring my GRPC instances. 
This was because the input type was set to url instead of text.
Even if the pattern passes the match test, the url would block as it requires a fully qualified domain name with HTTP and this would fail to submit
2023-03-01 19:45:31 +02:00
Michael Telgkamp
7b4f90ce92 Improve ntfy notifications
- use tags `red_circle` for down and `green_circle` for up
- increase priority for down alert by 1 if not already max
- add monitor name and status to title
- use heartbeat msg as Message
- add monitor url as action
2023-03-01 08:37:06 +01:00
titanventura
db6b863445 show tags in monitor select list under status page : change select UI from normal select to vue-multiselect 2023-02-26 16:26:09 +05:30
Louis Lam
d668812df1 Fix merge issue 2023-02-25 17:59:25 +08:00
Louis Lam
f32d3af62c Merge remote-tracking branch 'origin/master' into doubles-ss_master
# Conflicts:
#	server/database.js
2023-02-25 17:57:25 +08:00
Louis Lam
8a115670cd Fix label and id
Co-authored-by: AlexKraus <alex.b.kraus@googlemail.com>
2023-02-25 17:55:40 +08:00
Michael Telgkamp
186ca30508 Improve mattermost notifications 2023-02-23 17:40:39 +01:00
Ruben van Dijk
896e33815d Merge branch 'louislam:master' into fix-metrics-push 2023-02-23 14:11:39 +01:00
Peace
0be8b111e2 chore: better up message
Co-authored-by: Matthew Nickson <mnickson@sidingsmedia.com>
2023-02-20 13:48:16 +01:00
Peace
cef0a0faf4 Merge branch 'master' into group-monitors 2023-02-16 21:38:53 +01:00
Dave Baker
dfb95dfdcb Hostnames may be specified with a trailing dot to prevent DNS search lookups 2023-02-16 08:55:19 +00:00
Suriya Soutmun
43941fa2c6 feat: add mtls authen method in http/http keyword 2023-02-07 09:40:47 +07:00
Suriya Soutmun
faa78443d6 chore: alter table monitor add column tls_ca, tls_cert, tls_key for certificate data 2023-02-07 09:40:44 +07:00
Suriya Soutmun
ab3b2bddba [empty commit] pull request for http/http keyword mTLS authen 2023-02-07 09:37:55 +07:00
Peace
e10ba9ed7e Merge branch 'master' into group-monitors 2023-02-02 17:59:48 +01:00
Peace
9446c2d102 fix: use active instead of isActive in uploadBackup 2023-02-01 23:39:42 +01:00
Peace
2c581ade90 Merge branch 'louislam:master' into group-monitors 2023-02-01 20:44:09 +01:00
Peace
f286386f59 fix: add message for empty group pending state 2023-02-01 20:19:47 +01:00
Peace
9286dcb6ce fix: add serverside check against endless loops 2023-02-01 20:16:56 +01:00
Sebastian Kaempfe
a6894d36f2 [#2501] Dashboard: Details Page
- enable clickable URL on Dashboard Details if monitor is of type `mp-health`
2023-01-30 15:55:12 +01:00
Peace
66573934f6 refactor: remove old code 2023-01-28 15:21:17 +01:00
Peace
c444d78706 style: fix linting errors 2023-01-28 15:20:40 +01:00
Peace
661fa87134 feat: make parent link clickable 2023-01-28 14:53:40 +01:00
Peace
d48eb24046 docs: more comments 2023-01-28 14:28:53 +01:00
Peace
aee4c22dee perf: only do one filter instead of 3 in editMonitor 2023-01-28 14:28:34 +01:00
Peace
9a46b50989 docs: add comments 2023-01-28 14:22:15 +01:00
Peace
faf3488b1e fix: unfold tree if monitor is accessed directly 2023-01-28 14:15:25 +01:00
Peace
f3ac351d75 feat: set childs under maintenance if parent is too 2023-01-28 14:02:10 +01:00
Peace
aba515e172 feat: disable childs if parent is disabled 2023-01-28 13:39:17 +01:00
Peace
97bd306a09 Merge branch 'louislam:master' into group-monitors 2023-01-28 03:07:42 +01:00
Peace
645fd94bba feat: add ability to group monitors in dashboard 2023-01-28 02:58:03 +01:00
Ruben
71f00b3690 Parse push ping parameter with parseInt. 2023-01-12 18:33:39 +01:00
Sebastian Kaempfe
a21a47de93 [#2593] renamed the method sendCertNotification to better represent what id does. Evaluate certificate expiry from all certs in chain. Send a separate notification for every cert in chain, including cert type and CN. 2023-01-12 11:39:36 +01:00
Sebastian Kaempfe
f6d0f28b3a [#2593] during certificate evaluation also set the cert type for improved notifications 2023-01-12 11:34:37 +01:00
Ruben
9404efd86d Fixed the metrics for the push type. 2022-12-28 10:37:25 +01:00
122 changed files with 8108 additions and 18568 deletions

View File

@@ -26,6 +26,12 @@ body:
label: "📝 Describe your problem"
description: "Please walk us through it step by step."
placeholder: "Describe what are you asking for..."
- type: textarea
id: error-msg
validations:
required: false
attributes:
label: "📝 Error Message(s) or Log"
- type: input
id: uptime-kuma-version
attributes:

View File

@@ -61,8 +61,8 @@ body:
id: operating-system
attributes:
label: "💻 Operating System and Arch"
description: "Which OS is your server/device running on?"
placeholder: "Ex. Ubuntu 20.04 x86"
description: "Which OS is your server/device running on? (For Replit, please do not report this bug)"
placeholder: "Ex. Ubuntu 20.04 x64 "
validations:
required: true
- type: input

1
.github/config/exclude.txt vendored Normal file
View File

@@ -0,0 +1 @@
# This is a .gitignore style file for 'GrantBirki/json-yaml-validate' Action workflow

View File

@@ -22,7 +22,7 @@ jobs:
strategy:
matrix:
os: [macos-latest, ubuntu-latest, windows-latest]
node: [ 14, 16, 18, 19 ]
node: [ 14, 16, 18, 20 ]
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
steps:

View File

@@ -0,0 +1,26 @@
name: json-yaml-validate
on:
push:
branches:
- master
pull_request:
branches:
- master
workflow_dispatch:
permissions:
contents: read
pull-requests: write # enable write permissions for pull request comments
jobs:
json-yaml-validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: json-yaml-validate
id: json-yaml-validate
uses: GrantBirki/json-yaml-validate@v1.3.0
with:
comment: "true" # enable comment mode
exclude_file: ".github/config/exclude.txt" # gitignore style file for exclusions

3
.gitignore vendored
View File

@@ -23,3 +23,6 @@ cypress/screenshots
extra/exe-builder/bin
extra/exe-builder/obj
.vs
.vscode

View File

@@ -47,17 +47,17 @@ Here are some references:
❌ Won't Merge
- A dedicated pr for translating existing languages (You can now translate on https://weblate.kuma.pet)
- Do not pass auto test
- Do not pass the auto test
- Any breaking changes
- Duplicated pull request
- Duplicated pull requests
- Buggy
- UI/UX is not close to Uptime Kuma
- Existing logic is completely modified or deleted for no reason
- A function that is completely out of scope
- Convert existing code into other programming languages
- Unnecessary large code changes (Hard to review, causes code conflicts to other pull requests)
- Modifications or deletions of existing logic without a valid reason.
- Adding functions that is completely out of scope
- Converting existing code into other programming languages
- Unnecessarily large code changes that are hard to review and cause conflicts with other PRs.
The above cases cannot cover all situations.
The above cases may not cover all possible situations.
I (@louislam) have the final say. If your pull request does not meet my expectations, I will reject it, no matter how much time you spend on it. Therefore, it is essential to have a discussion beforehand.

View File

@@ -49,8 +49,12 @@ Uptime Kuma is now running on http://localhost:3001
### 💪🏻 Non-Docker
Required Tools:
- [Node.js](https://nodejs.org/en/download/) >= 14
Requirements:
- Platform
- ✅ Major Linux distros such as Debian, Ubuntu, CentOS, Fedora and ArchLinux etc.
- ✅ Windows 10 (x64), Windows Server 2012 R2 (x64) or higher
- ❌ Replit / Heroku
- [Node.js](https://nodejs.org/en/download/) 14 / 16 / 18 (20 is not supported)
- [npm](https://docs.npmjs.com/cli/) >= 7
- [Git](https://git-scm.com/downloads)
- [pm2](https://pm2.keymetrics.io/) - For running Uptime Kuma in the background
@@ -87,6 +91,10 @@ pm2 monit
pm2 save && pm2 startup
```
### Windows Portable (x64)
https://github.com/louislam/uptime-kuma/releases/download/1.21.0/uptime-kuma-win64-portable-1.0.0.zip
### Advanced Installation
If you need more options or need to browse via a reverse proxy, please read:
@@ -144,17 +152,18 @@ Telegram Notification Sample:
If you love this project, please consider giving me a ⭐.
## 🗣️ Discussion
## 🗣️ Discussion / Ask for Help
### Issues Page
⚠️ For any general or technical questions, please don't send me an email, as I am unable to provide support in that manner. I will not response if you asked such questions.
You can discuss or ask for help in [issues](https://github.com/louislam/uptime-kuma/issues).
I recommend using Google, GitHub Issues, or Uptime Kuma's Subreddit for finding answers to your question. If you cannot find the information you need, feel free to ask:
### Subreddit
- [GitHub Issues](https://github.com/louislam/uptime-kuma/issues)
- [Subreddit r/Uptime kuma](https://www.reddit.com/r/UptimeKuma/)
My Reddit account: [u/louislamlam](https://reddit.com/u/louislamlam).
You can mention me if you ask a question on Reddit.
[r/Uptime kuma](https://www.reddit.com/r/UptimeKuma/)
## Contribute

View File

@@ -0,0 +1,6 @@
BEGIN TRANSACTION;
ALTER TABLE monitor
ADD parent INTEGER REFERENCES [monitor] ([id]) ON DELETE SET NULL ON UPDATE CASCADE;
COMMIT

View File

@@ -0,0 +1,11 @@
-- You should not modify if this have pushed to Github, unless it does serious wrong with the db.
BEGIN TRANSACTION;
DROP TABLE maintenance_timeslot;
-- 999 characters. https://stackoverflow.com/questions/46134830/maximum-length-for-cron-job
ALTER TABLE maintenance ADD cron TEXT;
ALTER TABLE maintenance ADD timezone VARCHAR(255);
ALTER TABLE maintenance ADD duration INTEGER;
COMMIT;

13
db/patch-monitor-tls.sql Normal file
View File

@@ -0,0 +1,13 @@
-- You should not modify if this have pushed to Github, unless it does serious wrong with the db.
BEGIN TRANSACTION;
ALTER TABLE monitor
ADD tls_ca TEXT default null;
ALTER TABLE monitor
ADD tls_cert TEXT default null;
ALTER TABLE monitor
ADD tls_key TEXT default null;
COMMIT;

View File

@@ -4,5 +4,5 @@ WORKDIR /app
# Install apprise, iputils for non-root ping, setpriv
RUN apk add --no-cache iputils setpriv dumb-init python3 py3-cryptography py3-pip py3-six py3-yaml py3-click py3-markdown py3-requests py3-requests-oauthlib git && \
pip3 --no-cache-dir install apprise==1.3.0 && \
pip3 --no-cache-dir install apprise==1.4.0 && \
rm -rf /root/.cache

View File

@@ -8,21 +8,21 @@ WORKDIR /app
# Install Curl
# Install Apprise, add sqlite3 cli for debugging in the future, iputils-ping for ping, util-linux for setpriv
# Stupid python3 and python3-pip actually install a lot of useless things into Debian, specify --no-install-recommends to skip them, make the base even smaller than alpine!
RUN apt update && \
apt --yes --no-install-recommends install python3 python3-pip python3-cryptography python3-six python3-yaml python3-click python3-markdown python3-requests python3-requests-oauthlib \
sqlite3 iputils-ping util-linux dumb-init git && \
pip3 --no-cache-dir install apprise==1.3.0 && \
RUN apt-get update && \
apt-get --yes --no-install-recommends install python3 python3-pip python3-cryptography python3-six python3-yaml python3-click python3-markdown python3-requests python3-requests-oauthlib \
sqlite3 iputils-ping util-linux dumb-init git curl ca-certificates && \
pip3 --no-cache-dir install apprise==1.4.0 && \
rm -rf /var/lib/apt/lists/* && \
apt --yes autoremove
# Install cloudflared
# dpkg --add-architecture arm: cloudflared do not provide armhf, this is workaround. Read more: https://github.com/cloudflare/cloudflared/issues/583
COPY extra/download-cloudflared.js ./extra/download-cloudflared.js
RUN node ./extra/download-cloudflared.js $TARGETPLATFORM && \
dpkg --add-architecture arm && \
apt update && \
apt --yes --no-install-recommends install ./cloudflared.deb && \
RUN set -eux && \
mkdir -p --mode=0755 /usr/share/keyrings && \
curl --fail --show-error --silent --location --insecure https://pkg.cloudflare.com/cloudflare-main.gpg --output /usr/share/keyrings/cloudflare-main.gpg && \
echo 'deb [signed-by=/usr/share/keyrings/cloudflare-main.gpg] https://pkg.cloudflare.com/cloudflared buster main' | tee /etc/apt/sources.list.d/cloudflared.list && \
apt-get update && \
apt-get install --yes --no-install-recommends cloudflared && \
cloudflared version && \
rm -rf /var/lib/apt/lists/* && \
rm -f cloudflared.deb && \
apt --yes autoremove

View File

@@ -43,10 +43,11 @@ const prompt = (query) => new Promise((resolve) => rl.question(query, resolve));
});
console.log(result.stdout + result.stderr);
/*
result = await ssh.execCommand("pm2 restart 1", {
cwd,
});
console.log(result.stdout + result.stderr);
console.log(result.stdout + result.stderr);*/
} catch (e) {
console.log(e);

View File

@@ -1,48 +0,0 @@
//
const http = require("https"); // or 'https' for https:// URLs
const fs = require("fs");
const platform = process.argv[2];
if (!platform) {
console.error("No platform??");
process.exit(1);
}
let arch = null;
if (platform === "linux/amd64") {
arch = "amd64";
} else if (platform === "linux/arm64") {
arch = "arm64";
} else if (platform === "linux/arm/v7") {
arch = "arm";
} else {
console.error("Invalid platform?? " + platform);
}
const file = fs.createWriteStream("cloudflared.deb");
get("https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-" + arch + ".deb");
/**
* Download specified file
* @param {string} url URL to request
*/
function get(url) {
http.get(url, function (res) {
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
console.log("Redirect to " + res.headers.location);
get(res.headers.location);
} else if (res.statusCode >= 200 && res.statusCode < 300) {
res.pipe(file);
res.on("end", function () {
console.log("Downloaded");
});
} else {
console.error(res.statusCode);
process.exit(1);
}
});
}

View File

@@ -13,7 +13,7 @@ lines = lines.filter((line) => line !== "");
lines = [ ...new Set(lines) ];
// Remove @weblate and @UptimeKumaBot
lines = lines.filter((line) => line !== "@weblate" && line !== "@UptimeKumaBot");
lines = lines.filter((line) => line !== "@weblate" && line !== "@UptimeKumaBot" && line !== "@louislam");
// Sort the lines
lines = lines.sort();

View File

@@ -2,7 +2,7 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">
<link rel="icon" type="image/svg+xml" href="/icon.svg" />
<link rel="manifest" href="/manifest.json" />

20065
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,13 +1,13 @@
{
"name": "uptime-kuma",
"version": "1.21.0-beta.1",
"version": "1.22.0",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/louislam/uptime-kuma.git"
},
"engines": {
"node": "14.* || >=16.*"
"node": "14.* || 16.* || 18.*"
},
"scripts": {
"install-legacy": "npm install",
@@ -39,7 +39,7 @@
"build-docker-nightly-amd64": "docker buildx build -f docker/dockerfile --platform linux/amd64 -t louislam/uptime-kuma:nightly-amd64 --target nightly . --push --progress plain",
"build-docker-pr-test": "docker buildx build -f docker/dockerfile --platform linux/amd64,linux/arm64 -t louislam/uptime-kuma:pr-test --target pr-test . --push",
"upload-artifacts": "docker buildx build -f docker/dockerfile --platform linux/amd64 -t louislam/uptime-kuma:upload-artifact --build-arg VERSION --build-arg GITHUB_TOKEN --target upload-artifact . --progress plain",
"setup": "git checkout 1.20.2 && npm ci --production && npm run download-dist",
"setup": "git checkout 1.22.0 && npm ci --production && 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",
@@ -64,19 +64,18 @@
"cy:run:unit": "npx cypress run --browser chrome --headless --config-file ./config/cypress.frontend.config.js",
"cypress-open": "concurrently -k -r \"node test/prepare-test-server.js && node server/server.js --port=3002 --data-dir=./data/test/\" \"cypress open --config-file ./config/cypress.config.js\"",
"build-healthcheck-armv7": "cross-env GOOS=linux GOARCH=arm GOARM=7 go build -x -o ./extra/healthcheck-armv7 ./extra/healthcheck.go",
"depoly-demo-server": "node extra/deploy-demo-server.js",
"deploy-demo-server": "node extra/deploy-demo-server.js",
"sort-contributors": "node extra/sort-contributors.js"
},
"dependencies": {
"@grpc/grpc-js": "~1.7.3",
"@louislam/ping": "~0.4.2-mod.2",
"@louislam/sqlite3": "15.1.2",
"@louislam/ping": "~0.4.4-mod.0",
"@louislam/sqlite3": "15.1.6",
"args-parser": "~1.3.0",
"axios": "~0.27.0",
"axios-ntlm": "1.3.0",
"badge-maker": "~3.3.1",
"bcryptjs": "~2.4.3",
"bree": "~7.1.5",
"cacheable-lookup": "~6.0.4",
"chardet": "~1.4.0",
"check-password-strength": "^2.0.5",
@@ -85,13 +84,14 @@
"command-exists": "~1.2.9",
"compare-versions": "~3.6.0",
"compression": "~1.7.4",
"croner": "~6.0.5",
"dayjs": "~1.11.5",
"dotenv": "~16.0.3",
"express": "~4.17.3",
"express-basic-auth": "~1.2.1",
"express-static-gzip": "~2.1.7",
"form-data": "~4.0.0",
"gamedig": "^4.0.5",
"gamedig": "~4.0.5",
"http-graceful-shutdown": "~3.1.7",
"http-proxy-agent": "~5.0.0",
"https-proxy-agent": "~5.0.1",
@@ -104,7 +104,7 @@
"mqtt": "~4.3.7",
"mssql": "~8.1.4",
"mysql2": "~2.3.3",
"nanoid": "^3.3.4",
"nanoid": "~3.3.4",
"node-cloudflared-tunnel": "~1.0.9",
"node-radius-client": "~1.0.0",
"nodemailer": "~6.6.5",
@@ -116,10 +116,10 @@
"prometheus-api-metrics": "~3.2.1",
"protobufjs": "~7.1.1",
"qs": "~6.10.4",
"redbean-node": "~0.2.0",
"redbean-node": "~0.3.0",
"redis": "~4.5.1",
"socket.io": "~4.5.3",
"socket.io-client": "~4.5.3",
"socket.io": "~4.6.1",
"socket.io-client": "~4.6.1",
"socks-proxy-agent": "6.1.1",
"tar": "~6.1.11",
"tcp-ping": "~0.1.1",
@@ -142,10 +142,11 @@
"aedes": "^0.46.3",
"babel-plugin-rewire": "~1.2.0",
"bootstrap": "5.1.3",
"chart.js": "~3.6.2",
"chartjs-adapter-dayjs": "~1.0.0",
"chart.js": "~4.2.1",
"chartjs-adapter-dayjs-4": "~1.0.4",
"concurrently": "^7.1.0",
"core-js": "~3.26.1",
"cronstrue": "~2.24.0",
"cross-env": "~7.0.3",
"cypress": "^10.1.0",
"delay": "^5.0.0",
@@ -164,16 +165,16 @@
"qrcode": "~1.5.0",
"rollup-plugin-visualizer": "^5.6.0",
"sass": "~1.42.1",
"stylelint": "~14.7.1",
"stylelint": "~15.9.0",
"stylelint-config-standard": "~25.0.0",
"terser": "~5.15.0",
"timezones-list": "~3.0.1",
"typescript": "~4.4.4",
"v-pagination-3": "~0.1.7",
"vite": "~3.1.0",
"vite": "~3.2.7",
"vite-plugin-compression": "^0.5.1",
"vue": "next",
"vue-chart-3": "3.0.9",
"vue": "~3.2.47",
"vue-chartjs": "~5.2.0",
"vue-confirm-dialog": "~1.0.2",
"vue-contenteditable": "~3.0.4",
"vue-i18n": "~9.2.2",
@@ -184,6 +185,7 @@
"vue-router": "~4.0.14",
"vue-toastification": "~2.0.0-rc.5",
"vuedraggable": "~4.1.0",
"wait-on": "^6.0.1"
"wait-on": "^6.0.1",
"whatwg-url": "~12.0.1"
}
}

View File

@@ -2,6 +2,7 @@ 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");
const dayjs = require("dayjs");
@@ -81,12 +82,16 @@ function apiAuthorizer(username, password, callback) {
apiRateLimiter.pass(null, 0).then((pass) => {
if (pass) {
verifyAPIKey(password).then((valid) => {
if (!valid) {
log.warn("api-auth", "Failed API auth attempt: invalid API Key");
}
callback(null, valid);
// Only allow a set number of api requests per minute
// (currently set to 60)
apiRateLimiter.removeTokens(1);
});
} else {
log.warn("api-auth", "Failed API auth attempt: rate limit exceeded");
callback(null, false);
}
});
@@ -106,10 +111,12 @@ function userAuthorizer(username, password, callback) {
callback(null, user != null);
if (user == null) {
log.warn("basic-auth", "Failed basic auth attempt: invalid username/password");
loginRateLimiter.removeTokens(1);
}
});
} else {
log.warn("basic-auth", "Failed basic auth attempt: rate limit exceeded");
callback(null, false);
}
});

View File

@@ -2,7 +2,6 @@ const fs = require("fs");
const { R } = require("redbean-node");
const { setSetting, setting } = require("./util-server");
const { log, sleep } = require("../src/util");
const dayjs = require("dayjs");
const knex = require("knex");
const { PluginsManager } = require("./plugins-manager");
@@ -30,11 +29,6 @@ class Database {
*/
static patched = false;
/**
* For Backup only
*/
static backupPath = null;
/**
* Add patch filename in key
* Values:
@@ -73,6 +67,9 @@ class Database {
"patch-http-body-encoding.sql": true,
"patch-add-description-monitor.sql": true,
"patch-api-key-table.sql": true,
"patch-monitor-tls.sql": true,
"patch-maintenance-cron.sql": true,
"patch-add-parent-monitor.sql": true,
};
/**
@@ -197,15 +194,7 @@ class Database {
} else {
log.info("db", "Database patch is needed");
try {
this.backup(version);
} catch (e) {
log.error("db", e);
log.error("db", "Unable to create a backup before patching the database. Please make sure you have enough space and permission.");
process.exit(1);
}
// Try catch anything here, if gone wrong, restore the backup
// Try catch anything here
try {
for (let i = version + 1; i <= this.latestVersion; i++) {
const sqlFile = `./db/patch${i}.sql`;
@@ -221,7 +210,6 @@ class Database {
log.error("db", "Start Uptime-Kuma failed due to issue patching the database");
log.error("db", "Please submit a bug report if you still encounter the problem after restart: https://github.com/louislam/uptime-kuma/issues");
this.restore();
process.exit(1);
}
}
@@ -263,8 +251,6 @@ class Database {
log.error("db", "Start Uptime-Kuma failed due to issue patching the database");
log.error("db", "Please submit the bug report if you still encounter the problem after restart: https://github.com/louislam/uptime-kuma/issues");
this.restore();
process.exit(1);
}
@@ -366,8 +352,6 @@ class Database {
}
}
this.backup(dayjs().format("YYYYMMDDHHmmss"));
log.info("db", sqlFilename + " is patching");
this.patched = true;
await this.importSQLFile("./db/" + sqlFilename);
@@ -433,6 +417,9 @@ class Database {
log.info("db", "Closing the database");
// Flush WAL to main database
await R.exec("PRAGMA wal_checkpoint(TRUNCATE)");
while (true) {
Database.noReject = true;
await R.close();
@@ -449,100 +436,6 @@ class Database {
process.removeListener("unhandledRejection", listener);
}
/**
* One backup one time in this process.
* Reset this.backupPath if you want to backup again
* @param {string} version Version code of backup
*/
static backup(version) {
if (! this.backupPath) {
log.info("db", "Backing up the database");
this.backupPath = this.dataDir + "kuma.db.bak" + version;
fs.copyFileSync(Database.path, this.backupPath);
const shmPath = Database.path + "-shm";
if (fs.existsSync(shmPath)) {
this.backupShmPath = shmPath + ".bak" + version;
fs.copyFileSync(shmPath, this.backupShmPath);
}
const walPath = Database.path + "-wal";
if (fs.existsSync(walPath)) {
this.backupWalPath = walPath + ".bak" + version;
fs.copyFileSync(walPath, this.backupWalPath);
}
// Double confirm if all files actually backup
if (!fs.existsSync(this.backupPath)) {
throw new Error("Backup failed! " + this.backupPath);
}
if (fs.existsSync(shmPath)) {
if (!fs.existsSync(this.backupShmPath)) {
throw new Error("Backup failed! " + this.backupShmPath);
}
}
if (fs.existsSync(walPath)) {
if (!fs.existsSync(this.backupWalPath)) {
throw new Error("Backup failed! " + this.backupWalPath);
}
}
}
}
/** Restore from most recent backup */
static restore() {
if (this.backupPath) {
log.error("db", "Patching the database failed!!! Restoring the backup");
const shmPath = Database.path + "-shm";
const walPath = Database.path + "-wal";
// Make sure we have a backup to restore before deleting old db
if (
!fs.existsSync(this.backupPath)
&& !fs.existsSync(shmPath)
&& !fs.existsSync(walPath)
) {
log.error("db", "Backup file not found! Leaving database in failed state.");
process.exit(1);
}
// Delete patch failed db
try {
if (fs.existsSync(Database.path)) {
fs.unlinkSync(Database.path);
}
if (fs.existsSync(shmPath)) {
fs.unlinkSync(shmPath);
}
if (fs.existsSync(walPath)) {
fs.unlinkSync(walPath);
}
} catch (e) {
log.error("db", "Restore failed; you may need to restore the backup manually");
process.exit(1);
}
// Restore backup
fs.copyFileSync(this.backupPath, Database.path);
if (this.backupShmPath) {
fs.copyFileSync(this.backupShmPath, shmPath);
}
if (this.backupWalPath) {
fs.copyFileSync(this.backupWalPath, walPath);
}
} else {
log.info("db", "Nothing to restore");
}
}
/** Get the size of the database */
static getSize() {
log.debug("db", "Database.getSize()");

View File

@@ -1,41 +1,44 @@
const path = require("path");
const Bree = require("bree");
const { SHARE_ENV } = require("worker_threads");
const { log } = require("../src/util");
let bree;
const { UptimeKumaServer } = require("./uptime-kuma-server");
const { clearOldData } = require("./jobs/clear-old-data");
const Cron = require("croner");
const jobs = [
{
name: "clear-old-data",
interval: "at 03:14",
interval: "14 03 * * *",
jobFunc: clearOldData,
croner: null,
},
];
/**
* Initialize background jobs
* @param {Object} args Arguments to pass to workers
* @returns {Bree}
* @returns {Promise<void>}
*/
const initBackgroundJobs = function (args) {
bree = new Bree({
root: path.resolve("server", "jobs"),
jobs,
worker: {
env: SHARE_ENV,
workerData: args,
},
workerMessageHandler: (message) => {
log.info("jobs", message);
}
});
const initBackgroundJobs = async function () {
const timezone = await UptimeKumaServer.getInstance().getTimezone();
for (const job of jobs) {
const cornerJob = new Cron(
job.interval,
{
name: job.name,
timezone,
},
job.jobFunc,
);
job.croner = cornerJob;
}
bree.start();
return bree;
};
/** Stop all background jobs if running */
const stopBackgroundJobs = function () {
if (bree) {
bree.stop();
for (const job of jobs) {
if (job.croner) {
job.croner.stop();
job.croner = null;
}
}
};

View File

@@ -1,12 +1,15 @@
const { log, exit, connectDb } = require("./util-worker");
const { R } = require("redbean-node");
const { log } = require("../../src/util");
const { setSetting, setting } = require("../util-server");
const DEFAULT_KEEP_PERIOD = 180;
(async () => {
await connectDb();
/**
* Clears old data from the heartbeat table of the database.
* @return {Promise<void>} A promise that resolves when the data has been cleared.
*/
const clearOldData = async () => {
let period = await setting("keepDataPeriodDays");
// Set Default Period
@@ -20,16 +23,16 @@ const DEFAULT_KEEP_PERIOD = 180;
try {
parsedPeriod = parseInt(period);
} catch (_) {
log("Failed to parse setting, resetting to default..");
log.warn("clearOldData", "Failed to parse setting, resetting to default..");
await setSetting("keepDataPeriodDays", DEFAULT_KEEP_PERIOD, "general");
parsedPeriod = DEFAULT_KEEP_PERIOD;
}
if (parsedPeriod < 1) {
log(`Data deletion has been disabled as period is less than 1. Period is ${parsedPeriod} days.`);
log.info("clearOldData", `Data deletion has been disabled as period is less than 1. Period is ${parsedPeriod} days.`);
} else {
log(`Clearing Data older than ${parsedPeriod} days...`);
log.debug("clearOldData", `Clearing Data older than ${parsedPeriod} days...`);
try {
await R.exec(
@@ -37,9 +40,11 @@ const DEFAULT_KEEP_PERIOD = 180;
[ parsedPeriod ]
);
} catch (e) {
log(`Failed to clear old data: ${e.message}`);
log.error("clearOldData", `Failed to clear old data: ${e.message}`);
}
}
};
exit();
})();
module.exports = {
clearOldData,
};

View File

@@ -1,50 +0,0 @@
const { parentPort, workerData } = require("worker_threads");
const Database = require("../database");
const path = require("path");
/**
* Send message to parent process for logging
* since worker_thread does not have access to stdout, this is used
* instead of console.log()
* @param {any} any The message to log
*/
const log = function (any) {
if (parentPort) {
parentPort.postMessage(any);
}
};
/**
* Exit the worker process
* @param {number} error The status code to exit
*/
const exit = function (error) {
if (error && error !== 0) {
process.exit(error);
} else {
if (parentPort) {
parentPort.postMessage("done");
} else {
process.exit(0);
}
}
};
/** Connects to the database */
const connectDb = async function () {
const dbPath = path.join(
process.env.DATA_DIR || workerData["data-dir"] || "./data/"
);
Database.init({
"data-dir": dbPath,
});
await Database.connect();
};
module.exports = {
log,
exit,
connectDb,
};

View File

@@ -1,8 +1,10 @@
const { BeanModel } = require("redbean-node/dist/bean-model");
const { parseTimeObject, parseTimeFromTimeObject, utcToLocal, localToUTC, log } = require("../../src/util");
const { timeObjectToUTC, timeObjectToLocal } = require("../util-server");
const { parseTimeObject, parseTimeFromTimeObject, log } = require("../../src/util");
const { R } = require("redbean-node");
const dayjs = require("dayjs");
const Cron = require("croner");
const { UptimeKumaServer } = require("../uptime-kuma-server");
const apicache = require("../modules/apicache");
class Maintenance extends BeanModel {
@@ -15,16 +17,19 @@ class Maintenance extends BeanModel {
let dateRange = [];
if (this.start_date) {
dateRange.push(utcToLocal(this.start_date));
if (this.end_date) {
dateRange.push(utcToLocal(this.end_date));
}
dateRange.push(this.start_date);
} else {
dateRange.push(null);
}
if (this.end_date) {
dateRange.push(this.end_date);
}
let timeRange = [];
let startTime = timeObjectToLocal(parseTimeObject(this.start_time));
let startTime = parseTimeObject(this.start_time);
timeRange.push(startTime);
let endTime = timeObjectToLocal(parseTimeObject(this.end_time));
let endTime = parseTimeObject(this.end_time);
timeRange.push(endTime);
let obj = {
@@ -39,12 +44,44 @@ class Maintenance extends BeanModel {
weekdays: (this.weekdays) ? JSON.parse(this.weekdays) : [],
daysOfMonth: (this.days_of_month) ? JSON.parse(this.days_of_month) : [],
timeslotList: [],
cron: this.cron,
duration: this.duration,
durationMinutes: parseInt(this.duration / 60),
timezone: await this.getTimezone(), // Only valid timezone
timezoneOption: this.timezone, // Mainly for dropdown menu, because there is a option "SAME_AS_SERVER"
timezoneOffset: await this.getTimezoneOffset(),
status: await this.getStatus(),
};
const timeslotList = await this.getTimeslotList();
if (this.strategy === "manual") {
// Do nothing, no timeslots
} else if (this.strategy === "single") {
obj.timeslotList.push({
startDate: this.start_date,
endDate: this.end_date,
});
} else {
// Should be cron or recurring here
if (this.beanMeta.job) {
let runningTimeslot = this.getRunningTimeslot();
for (let timeslot of timeslotList) {
obj.timeslotList.push(await timeslot.toPublicJSON());
if (runningTimeslot) {
obj.timeslotList.push(runningTimeslot);
}
let nextRunDate = this.beanMeta.job.nextRun();
if (nextRunDate) {
let startDateDayjs = dayjs(nextRunDate);
let startDate = startDateDayjs.toISOString();
let endDate = startDateDayjs.add(this.duration, "second").toISOString();
obj.timeslotList.push({
startDate,
endDate,
});
}
}
}
if (!Array.isArray(obj.weekdays)) {
@@ -55,54 +92,9 @@ class Maintenance extends BeanModel {
obj.daysOfMonth = [];
}
// Maintenance Status
if (!obj.active) {
obj.status = "inactive";
} else if (obj.strategy === "manual") {
obj.status = "under-maintenance";
} else if (obj.timeslotList.length > 0) {
let currentTimestamp = dayjs().unix();
for (let timeslot of obj.timeslotList) {
if (dayjs.utc(timeslot.startDate).unix() <= currentTimestamp && dayjs.utc(timeslot.endDate).unix() >= currentTimestamp) {
log.debug("timeslot", "Timeslot ID: " + timeslot.id);
log.debug("timeslot", "currentTimestamp:" + currentTimestamp);
log.debug("timeslot", "timeslot.start_date:" + dayjs.utc(timeslot.startDate).unix());
log.debug("timeslot", "timeslot.end_date:" + dayjs.utc(timeslot.endDate).unix());
obj.status = "under-maintenance";
break;
}
}
if (!obj.status) {
obj.status = "scheduled";
}
} else if (obj.timeslotList.length === 0) {
obj.status = "ended";
} else {
obj.status = "unknown";
}
return obj;
}
/**
* Only get future or current timeslots only
* @returns {Promise<[]>}
*/
async getTimeslotList() {
return R.convertToBeans("maintenance_timeslot", await R.getAll(`
SELECT maintenance_timeslot.*
FROM maintenance_timeslot, maintenance
WHERE maintenance_timeslot.maintenance_id = maintenance.id
AND maintenance.id = ?
AND ${Maintenance.getActiveAndFutureMaintenanceSQLCondition()}
`, [
this.id
]));
}
/**
* Return an object that ready to parse to JSON
* @param {string} timezone If not specified, the timeRange will be in UTC
@@ -126,7 +118,7 @@ class Maintenance extends BeanModel {
/**
* Get a list of days in month that maintenance is active for
* @returns {number[]} Array of active days in month
* @returns {number[]|string[]} Array of active days in month
*/
getDayOfMonthList() {
return JSON.parse(this.days_of_month).sort(function (a, b) {
@@ -135,26 +127,10 @@ class Maintenance extends BeanModel {
}
/**
* Get the start date and time for maintenance
* @returns {dayjs.Dayjs} Start date and time
*/
getStartDateTime() {
let startOfTheDay = dayjs.utc(this.start_date).format("HH:mm");
log.debug("timeslot", "startOfTheDay: " + startOfTheDay);
// Start Time
let startTimeSecond = dayjs.utc(this.start_time, "HH:mm").diff(dayjs.utc(startOfTheDay, "HH:mm"), "second");
log.debug("timeslot", "startTime: " + startTimeSecond);
// Bake StartDate + StartTime = Start DateTime
return dayjs.utc(this.start_date).add(startTimeSecond, "second");
}
/**
* Get the duraction of maintenance in seconds
* Get the duration of maintenance in seconds
* @returns {number} Duration of maintenance
*/
getDuration() {
calcDuration() {
let duration = dayjs.utc(this.end_time, "HH:mm").diff(dayjs.utc(this.start_time, "HH:mm"), "second");
// Add 24hours if it is across day
if (duration < 0) {
@@ -169,71 +145,270 @@ class Maintenance extends BeanModel {
* @param {Object} obj Data to fill bean with
* @returns {Bean} Filled bean
*/
static jsonToBean(bean, obj) {
static async jsonToBean(bean, obj) {
if (obj.id) {
bean.id = obj.id;
}
// Apply timezone offset to timeRange, as it cannot apply automatically.
if (obj.timeRange[0]) {
timeObjectToUTC(obj.timeRange[0]);
if (obj.timeRange[1]) {
timeObjectToUTC(obj.timeRange[1]);
}
}
bean.title = obj.title;
bean.description = obj.description;
bean.strategy = obj.strategy;
bean.interval_day = obj.intervalDay;
bean.timezone = obj.timezoneOption;
bean.active = obj.active;
if (obj.dateRange[0]) {
bean.start_date = localToUTC(obj.dateRange[0]);
if (obj.dateRange[1]) {
bean.end_date = localToUTC(obj.dateRange[1]);
}
bean.start_date = obj.dateRange[0];
} else {
bean.start_date = null;
}
bean.start_time = parseTimeFromTimeObject(obj.timeRange[0]);
bean.end_time = parseTimeFromTimeObject(obj.timeRange[1]);
if (obj.dateRange[1]) {
bean.end_date = obj.dateRange[1];
} else {
bean.end_date = null;
}
bean.weekdays = JSON.stringify(obj.weekdays);
bean.days_of_month = JSON.stringify(obj.daysOfMonth);
if (bean.strategy === "cron") {
bean.duration = obj.durationMinutes * 60;
bean.cron = obj.cron;
this.validateCron(bean.cron);
}
if (bean.strategy.startsWith("recurring-")) {
bean.start_time = parseTimeFromTimeObject(obj.timeRange[0]);
bean.end_time = parseTimeFromTimeObject(obj.timeRange[1]);
bean.weekdays = JSON.stringify(obj.weekdays);
bean.days_of_month = JSON.stringify(obj.daysOfMonth);
await bean.generateCron();
this.validateCron(bean.cron);
}
return bean;
}
/**
* SQL conditions for active maintenance
* @returns {string}
* Throw error if cron is invalid
* @param cron
* @returns {Promise<void>}
*/
static getActiveMaintenanceSQLCondition() {
return `
(
(maintenance_timeslot.start_date <= DATETIME('now')
AND maintenance_timeslot.end_date >= DATETIME('now')
AND maintenance.active = 1)
OR
(maintenance.strategy = 'manual' AND active = 1)
)
`;
static async validateCron(cron) {
let job = new Cron(cron, () => {});
job.stop();
}
/**
* SQL conditions for active and future maintenance
* @returns {string}
* Run the cron
*/
static getActiveAndFutureMaintenanceSQLCondition() {
return `
(
((maintenance_timeslot.end_date >= DATETIME('now')
AND maintenance.active = 1)
OR
(maintenance.strategy = 'manual' AND active = 1))
)
`;
async run(throwError = false) {
if (this.beanMeta.job) {
log.debug("maintenance", "Maintenance is already running, stop it first. id: " + this.id);
this.stop();
}
log.debug("maintenance", "Run maintenance id: " + this.id);
// 1.21.2 migration
if (!this.cron) {
await this.generateCron();
if (!this.timezone) {
this.timezone = "UTC";
}
if (this.cron) {
await R.store(this);
}
}
if (this.strategy === "manual") {
// Do nothing, because it is controlled by the user
} else if (this.strategy === "single") {
this.beanMeta.job = new Cron(this.start_date, { timezone: await this.getTimezone() }, () => {
log.info("maintenance", "Maintenance id: " + this.id + " is under maintenance now");
UptimeKumaServer.getInstance().sendMaintenanceListByUserID(this.user_id);
apicache.clear();
});
} else if (this.cron != null) {
// Here should be cron or recurring
try {
this.beanMeta.status = "scheduled";
let startEvent = (customDuration = 0) => {
log.info("maintenance", "Maintenance id: " + this.id + " is under maintenance now");
this.beanMeta.status = "under-maintenance";
clearTimeout(this.beanMeta.durationTimeout);
// Check if duration is still in the window. If not, use the duration from the current time to the end of the window
let duration;
if (customDuration > 0) {
duration = customDuration;
} else if (this.end_date) {
let d = dayjs(this.end_date).diff(dayjs(), "second");
if (d < this.duration) {
duration = d * 1000;
}
} else {
duration = this.duration * 1000;
}
UptimeKumaServer.getInstance().sendMaintenanceListByUserID(this.user_id);
this.beanMeta.durationTimeout = setTimeout(() => {
// End of maintenance for this timeslot
this.beanMeta.status = "scheduled";
UptimeKumaServer.getInstance().sendMaintenanceListByUserID(this.user_id);
}, duration);
};
// Create Cron
this.beanMeta.job = new Cron(this.cron, {
timezone: await this.getTimezone(),
}, startEvent);
// Continue if the maintenance is still in the window
let runningTimeslot = this.getRunningTimeslot();
let current = dayjs();
if (runningTimeslot) {
let duration = dayjs(runningTimeslot.endDate).diff(current, "second") * 1000;
log.debug("maintenance", "Maintenance id: " + this.id + " Remaining duration: " + duration + "ms");
startEvent(duration);
}
} catch (e) {
log.error("maintenance", "Error in maintenance id: " + this.id);
log.error("maintenance", "Cron: " + this.cron);
log.error("maintenance", e);
if (throwError) {
throw e;
}
}
} else {
log.error("maintenance", "Maintenance id: " + this.id + " has no cron");
}
}
getRunningTimeslot() {
let start = dayjs(this.beanMeta.job.nextRun(dayjs().add(-this.duration, "second").toDate()));
let end = start.add(this.duration, "second");
let current = dayjs();
if (current.isAfter(start) && current.isBefore(end)) {
return {
startDate: start.toISOString(),
endDate: end.toISOString(),
};
} else {
return null;
}
}
stop() {
if (this.beanMeta.job) {
this.beanMeta.job.stop();
delete this.beanMeta.job;
}
}
async isUnderMaintenance() {
return (await this.getStatus()) === "under-maintenance";
}
async getTimezone() {
if (!this.timezone || this.timezone === "SAME_AS_SERVER") {
return await UptimeKumaServer.getInstance().getTimezone();
}
return this.timezone;
}
async getTimezoneOffset() {
return dayjs.tz(dayjs(), await this.getTimezone()).format("Z");
}
async getStatus() {
if (!this.active) {
return "inactive";
}
if (this.strategy === "manual") {
return "under-maintenance";
}
// Check if the maintenance is started
if (this.start_date && dayjs().isBefore(dayjs.tz(this.start_date, await this.getTimezone()))) {
return "scheduled";
}
// Check if the maintenance is ended
if (this.end_date && dayjs().isAfter(dayjs.tz(this.end_date, await this.getTimezone()))) {
return "ended";
}
if (this.strategy === "single") {
return "under-maintenance";
}
if (!this.beanMeta.status) {
return "unknown";
}
return this.beanMeta.status;
}
/**
* Generate Cron for recurring maintenance
* @returns {Promise<void>}
*/
async generateCron() {
log.info("maintenance", "Generate cron for maintenance id: " + this.id);
if (this.strategy === "cron") {
// Do nothing for cron
} else if (!this.strategy.startsWith("recurring-")) {
this.cron = "";
} else if (this.strategy === "recurring-interval") {
let array = this.start_time.split(":");
let hour = parseInt(array[0]);
let minute = parseInt(array[1]);
this.cron = minute + " " + hour + " */" + this.interval_day + " * *";
this.duration = this.calcDuration();
log.debug("maintenance", "Cron: " + this.cron);
log.debug("maintenance", "Duration: " + this.duration);
} else if (this.strategy === "recurring-weekday") {
let list = this.getDayOfWeekList();
let array = this.start_time.split(":");
let hour = parseInt(array[0]);
let minute = parseInt(array[1]);
this.cron = minute + " " + hour + " * * " + list.join(",");
this.duration = this.calcDuration();
} else if (this.strategy === "recurring-day-of-month") {
let list = this.getDayOfMonthList();
let array = this.start_time.split(":");
let hour = parseInt(array[0]);
let minute = parseInt(array[1]);
let dayList = [];
for (let day of list) {
if (typeof day === "string" && day.startsWith("lastDay")) {
if (day === "lastDay1") {
dayList.push("L");
}
// Unfortunately, lastDay2-4 is not supported by cron
} else {
dayList.push(day);
}
}
// Remove duplicate
dayList = [ ...new Set(dayList) ];
this.cron = minute + " " + hour + " " + dayList.join(",") + " * *";
this.duration = this.calcDuration();
}
}
}

View File

@@ -1,223 +0,0 @@
const { BeanModel } = require("redbean-node/dist/bean-model");
const { R } = require("redbean-node");
const dayjs = require("dayjs");
const { log, utcToLocal, SQL_DATETIME_FORMAT_WITHOUT_SECOND, localToUTC } = require("../../src/util");
const { UptimeKumaServer } = require("../uptime-kuma-server");
class MaintenanceTimeslot extends BeanModel {
/**
* Return an object that ready to parse to JSON for public
* Only show necessary data to public
* @returns {Object}
*/
async toPublicJSON() {
const serverTimezoneOffset = UptimeKumaServer.getInstance().getTimezoneOffset();
const obj = {
id: this.id,
startDate: this.start_date,
endDate: this.end_date,
startDateServerTimezone: utcToLocal(this.start_date, SQL_DATETIME_FORMAT_WITHOUT_SECOND),
endDateServerTimezone: utcToLocal(this.end_date, SQL_DATETIME_FORMAT_WITHOUT_SECOND),
serverTimezoneOffset,
};
return obj;
}
/**
* Return an object that ready to parse to JSON
* @returns {Object}
*/
async toJSON() {
return await this.toPublicJSON();
}
/**
* @param {Maintenance} maintenance
* @param {dayjs} minDate (For recurring type only) Generate a next timeslot from this date.
* @param {boolean} removeExist Remove existing timeslot before create
* @returns {Promise<MaintenanceTimeslot>}
*/
static async generateTimeslot(maintenance, minDate = null, removeExist = false) {
log.info("maintenance", "Generate Timeslot for maintenance id: " + maintenance.id);
if (removeExist) {
await R.exec("DELETE FROM maintenance_timeslot WHERE maintenance_id = ? ", [
maintenance.id
]);
}
if (maintenance.strategy === "manual") {
log.debug("maintenance", "No need to generate timeslot for manual type");
} else if (maintenance.strategy === "single") {
let bean = R.dispense("maintenance_timeslot");
bean.maintenance_id = maintenance.id;
bean.start_date = maintenance.start_date;
bean.end_date = maintenance.end_date;
bean.generated_next = true;
if (!await this.isDuplicateTimeslot(bean)) {
await R.store(bean);
return bean;
} else {
log.debug("maintenance", "Duplicate timeslot, skip");
return null;
}
} else if (maintenance.strategy === "recurring-interval") {
// Prevent dead loop, in case interval_day is not set
if (!maintenance.interval_day || maintenance.interval_day <= 0) {
maintenance.interval_day = 1;
}
return await this.handleRecurringType(maintenance, minDate, (startDateTime) => {
return startDateTime.add(maintenance.interval_day, "day");
}, () => {
return true;
});
} else if (maintenance.strategy === "recurring-weekday") {
let dayOfWeekList = maintenance.getDayOfWeekList();
log.debug("timeslot", dayOfWeekList);
if (dayOfWeekList.length <= 0) {
log.debug("timeslot", "No weekdays selected?");
return null;
}
const isValid = (startDateTime) => {
log.debug("timeslot", "nextDateTime: " + startDateTime);
let day = startDateTime.local().day();
log.debug("timeslot", "nextDateTime.day(): " + day);
return dayOfWeekList.includes(day);
};
return await this.handleRecurringType(maintenance, minDate, (startDateTime) => {
while (true) {
startDateTime = startDateTime.add(1, "day");
if (isValid(startDateTime)) {
return startDateTime;
}
}
}, isValid);
} else if (maintenance.strategy === "recurring-day-of-month") {
let dayOfMonthList = maintenance.getDayOfMonthList();
if (dayOfMonthList.length <= 0) {
log.debug("timeslot", "No day selected?");
return null;
}
const isValid = (startDateTime) => {
let day = parseInt(startDateTime.local().format("D"));
log.debug("timeslot", "day: " + day);
// Check 1-31
if (dayOfMonthList.includes(day)) {
return startDateTime;
}
// Check "lastDay1","lastDay2"...
let daysInMonth = startDateTime.daysInMonth();
let lastDayList = [];
// Small first, e.g. 28 > 29 > 30 > 31
for (let i = 4; i >= 1; i--) {
if (dayOfMonthList.includes("lastDay" + i)) {
lastDayList.push(daysInMonth - i + 1);
}
}
log.debug("timeslot", lastDayList);
return lastDayList.includes(day);
};
return await this.handleRecurringType(maintenance, minDate, (startDateTime) => {
while (true) {
startDateTime = startDateTime.add(1, "day");
if (isValid(startDateTime)) {
return startDateTime;
}
}
}, isValid);
} else {
throw new Error("Unknown maintenance strategy");
}
}
static async isDuplicateTimeslot(timeslot) {
let bean = await R.findOne("maintenance_timeslot", "maintenance_id = ? AND start_date = ? AND end_date = ?", [
timeslot.maintenance_id,
timeslot.start_date,
timeslot.end_date
]);
return bean !== null;
}
/**
* Generate a next timeslot for all recurring types
* @param maintenance
* @param minDate
* @param {function} nextDayCallback The logic how to get the next possible day
* @param {function} isValidCallback Check the day whether is matched the current strategy
* @returns {Promise<null|MaintenanceTimeslot>}
*/
static async handleRecurringType(maintenance, minDate, nextDayCallback, isValidCallback) {
let bean = R.dispense("maintenance_timeslot");
let duration = maintenance.getDuration();
let startDateTime = maintenance.getStartDateTime();
let endDateTime;
// Keep generating from the first possible date, until it is ok
while (true) {
//log.debug("timeslot", "startDateTime: " + startDateTime.format());
// Handling out of effective date range
if (startDateTime.diff(dayjs.utc(maintenance.end_date)) > 0) {
log.debug("timeslot", "Out of effective date range");
return null;
}
endDateTime = startDateTime.add(duration, "second");
// If endDateTime is out of effective date range, use the end datetime from effective date range
if (endDateTime.diff(dayjs.utc(maintenance.end_date)) > 0) {
endDateTime = dayjs.utc(maintenance.end_date);
}
// If minDate is set, the endDateTime must be bigger than it.
// And the endDateTime must be bigger current time
// Is valid under current recurring strategy
if (
(!minDate || endDateTime.diff(minDate) > 0) &&
endDateTime.diff(dayjs()) > 0 &&
isValidCallback(startDateTime)
) {
break;
}
startDateTime = nextDayCallback(startDateTime);
}
bean.maintenance_id = maintenance.id;
bean.start_date = localToUTC(startDateTime);
bean.end_date = localToUTC(endDateTime);
bean.generated_next = false;
if (!await this.isDuplicateTimeslot(bean)) {
await R.store(bean);
return bean;
} else {
log.debug("maintenance", "Duplicate timeslot, skip");
return null;
}
}
}
module.exports = MaintenanceTimeslot;

View File

@@ -2,7 +2,9 @@ const https = require("https");
const dayjs = require("dayjs");
const axios = require("axios");
const { Prometheus } = require("../prometheus");
const { log, UP, DOWN, PENDING, MAINTENANCE, flipStatus, TimeLogger, MAX_INTERVAL_SECOND, MIN_INTERVAL_SECOND } = require("../../src/util");
const { log, UP, DOWN, PENDING, MAINTENANCE, flipStatus, TimeLogger, MAX_INTERVAL_SECOND, MIN_INTERVAL_SECOND,
SQL_DATETIME_FORMAT
} = require("../../src/util");
const { tcping, ping, dnsResolve, checkCertificate, checkStatusCode, getTotalClientInRoom, setting, mssqlQuery, postgresQuery, mysqlQuery, mqttAsync, setSetting, httpNtlm, radius, grpcQuery,
redisPingAsync, mongodbPing,
} = require("../util-server");
@@ -16,7 +18,6 @@ const apicache = require("../modules/apicache");
const { UptimeKumaServer } = require("../uptime-kuma-server");
const { CacheableDnsHttpAgent } = require("../cacheable-dns-http-agent");
const { DockerHost } = require("../docker");
const Maintenance = require("./maintenance");
const { UptimeCacheList } = require("../uptime-cache-list");
const Gamedig = require("gamedig");
@@ -73,13 +74,17 @@ class Monitor extends BeanModel {
id: this.id,
name: this.name,
description: this.description,
pathName: await this.getPathName(),
parent: this.parent,
childrenIDs: await Monitor.getAllChildrenIDs(this.id),
url: this.url,
method: this.method,
hostname: this.hostname,
port: this.port,
maxretries: this.maxretries,
weight: this.weight,
active: this.active,
active: await this.isActive(),
forceInactive: !await Monitor.isParentActive(this.id),
type: this.type,
interval: this.interval,
retryInterval: this.retryInterval,
@@ -133,6 +138,9 @@ class Monitor extends BeanModel {
mqttPassword: this.mqttPassword,
authWorkstation: this.authWorkstation,
authDomain: this.authDomain,
tlsCa: this.tlsCa,
tlsCert: this.tlsCert,
tlsKey: this.tlsKey,
};
}
@@ -140,6 +148,16 @@ class Monitor extends BeanModel {
return data;
}
/**
* Checks if the monitor is active based on itself and its parents
* @returns {Promise<Boolean>}
*/
async isActive() {
const parentActive = await Monitor.isParentActive(this.id);
return this.active && parentActive;
}
/**
* Get all tags applied to this monitor
* @returns {Promise<LooseObject<any>[]>}
@@ -255,6 +273,36 @@ class Monitor extends BeanModel {
if (await Monitor.isUnderMaintenance(this.id)) {
bean.msg = "Monitor under maintenance";
bean.status = MAINTENANCE;
} else if (this.type === "group") {
const children = await Monitor.getChildren(this.id);
if (children.length > 0) {
bean.status = UP;
bean.msg = "All children up and running";
for (const child of children) {
if (!child.active) {
// Ignore inactive childs
continue;
}
const lastBeat = await Monitor.getPreviousHeartbeat(child.id);
// Only change state if the monitor is in worse conditions then the ones before
if (bean.status === UP && (lastBeat.status === PENDING || lastBeat.status === DOWN)) {
bean.status = lastBeat.status;
} else if (bean.status === PENDING && lastBeat.status === DOWN) {
bean.status = lastBeat.status;
}
}
if (bean.status !== UP) {
bean.msg = "Child inaccessible";
}
} else {
// Set status pending if group is empty
bean.status = PENDING;
bean.msg = "Group empty";
}
} else if (this.type === "http" || this.type === "keyword") {
// Do not do any queries/high loading things before the "bean.ping"
let startTime = dayjs().valueOf();
@@ -331,6 +379,18 @@ class Monitor extends BeanModel {
options.httpsAgent = new https.Agent(httpsAgentOptions);
}
if (this.auth_method === "mtls") {
if (this.tlsCert !== null && this.tlsCert !== "") {
options.httpsAgent.options.cert = Buffer.from(this.tlsCert);
}
if (this.tlsCa !== null && this.tlsCa !== "") {
options.httpsAgent.options.ca = Buffer.from(this.tlsCa);
}
if (this.tlsKey !== null && this.tlsKey !== "") {
options.httpsAgent.options.key = Buffer.from(this.tlsKey);
}
}
log.debug("monitor", `[${this.name}] Axios Options: ${JSON.stringify(options)}`);
log.debug("monitor", `[${this.name}] Axios Request`);
@@ -349,8 +409,8 @@ class Monitor extends BeanModel {
tlsInfo = await this.updateTlsInfo(tlsInfoObject);
if (!this.getIgnoreTls() && this.isEnabledExpiryNotification()) {
log.debug("monitor", `[${this.name}] call sendCertNotification`);
await this.sendCertNotification(tlsInfoObject);
log.debug("monitor", `[${this.name}] call checkCertExpiryNotifications`);
await this.checkCertExpiryNotifications(tlsInfoObject);
}
} catch (e) {
@@ -384,7 +444,7 @@ class Monitor extends BeanModel {
bean.msg += ", keyword is found";
bean.status = UP;
} else {
data = data.replace(/<[^>]*>?|[\n\r]|\s+/gm, " ");
data = data.replace(/<[^>]*>?|[\n\r]|\s+/gm, " ").trim();
if (data.length > 50) {
data = data.substring(0, 47) + "...";
}
@@ -622,9 +682,7 @@ class Monitor extends BeanModel {
} else if (this.type === "mysql") {
let startTime = dayjs().valueOf();
await mysqlQuery(this.databaseConnectionString, this.databaseQuery);
bean.msg = "";
bean.msg = await mysqlQuery(this.databaseConnectionString, this.databaseQuery);
bean.status = UP;
bean.ping = dayjs().valueOf() - startTime;
} else if (this.type === "mongodb") {
@@ -836,7 +894,6 @@ class Monitor extends BeanModel {
domain: this.authDomain,
workstation: this.authWorkstation ? this.authWorkstation : undefined
});
} else {
res = await axios.request(options);
}
@@ -1165,12 +1222,18 @@ class Monitor extends BeanModel {
for (let notification of notificationList) {
try {
// Prevent if the msg is undefined, notifications such as Discord cannot send out.
const heartbeatJSON = bean.toJSON();
// Prevent if the msg is undefined, notifications such as Discord cannot send out.
if (!heartbeatJSON["msg"]) {
heartbeatJSON["msg"] = "N/A";
}
// Also provide the time in server timezone
heartbeatJSON["timezone"] = await UptimeKumaServer.getInstance().getTimezone();
heartbeatJSON["timezoneOffset"] = UptimeKumaServer.getInstance().getTimezoneOffset();
heartbeatJSON["localDateTime"] = dayjs.utc(heartbeatJSON["time"]).tz(heartbeatJSON["timezone"]).format(SQL_DATETIME_FORMAT);
await Notification.send(JSON.parse(notification.config), msg, await monitor.toJSON(false), heartbeatJSON);
} catch (e) {
log.error("monitor", "Cannot send notification to " + notification.name);
@@ -1193,13 +1256,19 @@ class Monitor extends BeanModel {
}
/**
* Send notification about a certificate
* checks certificate chain for expiring certificates
* @param {Object} tlsInfoObject Information about certificate
*/
async sendCertNotification(tlsInfoObject) {
async checkCertExpiryNotifications(tlsInfoObject) {
if (tlsInfoObject && tlsInfoObject.certInfo && tlsInfoObject.certInfo.daysRemaining) {
const notificationList = await Monitor.getNotificationList(this);
if (! notificationList.length > 0) {
// fail fast. If no notification is set, all the following checks can be skipped.
log.debug("monitor", "No notification, no need to send cert notification");
return;
}
let notifyDays = await setting("tlsExpiryNotifyDays");
if (notifyDays == null || !Array.isArray(notifyDays)) {
// Reset Default
@@ -1207,10 +1276,19 @@ class Monitor extends BeanModel {
notifyDays = [ 7, 14, 21 ];
}
if (notifyDays != null && Array.isArray(notifyDays)) {
for (const day of notifyDays) {
log.debug("monitor", "call sendCertNotificationByTargetDays", day);
await this.sendCertNotificationByTargetDays(tlsInfoObject.certInfo.daysRemaining, day, notificationList);
if (Array.isArray(notifyDays)) {
for (const targetDays of notifyDays) {
let certInfo = tlsInfoObject.certInfo;
while (certInfo) {
let subjectCN = certInfo.subject["CN"];
if (certInfo.daysRemaining > targetDays) {
log.debug("monitor", `No need to send cert notification for ${certInfo.certType} certificate "${subjectCN}" (${certInfo.daysRemaining} days valid) on ${targetDays} deadline.`);
} else {
log.debug("monitor", `call sendCertNotificationByTargetDays for ${targetDays} deadline on certificate ${subjectCN}.`);
await this.sendCertNotificationByTargetDays(subjectCN, certInfo.certType, certInfo.daysRemaining, targetDays, notificationList);
}
certInfo = certInfo.issuerCertificate;
}
}
}
}
@@ -1219,55 +1297,47 @@ class Monitor extends BeanModel {
/**
* Send a certificate notification when certificate expires in less
* than target days
* @param {number} daysRemaining Number of days remaining on certifcate
* @param {string} certCN Common Name attribute from the certificate subject
* @param {string} certType certificate type
* @param {number} daysRemaining Number of days remaining on certificate
* @param {number} targetDays Number of days to alert after
* @param {LooseObject<any>[]} notificationList List of notification providers
* @returns {Promise<void>}
*/
async sendCertNotificationByTargetDays(daysRemaining, targetDays, notificationList) {
async sendCertNotificationByTargetDays(certCN, certType, daysRemaining, targetDays, notificationList) {
if (daysRemaining > targetDays) {
log.debug("monitor", `No need to send cert notification. ${daysRemaining} > ${targetDays}`);
let row = await R.getRow("SELECT * FROM notification_sent_history WHERE type = ? AND monitor_id = ? AND days <= ?", [
"certificate",
this.id,
targetDays,
]);
// Sent already, no need to send again
if (row) {
log.debug("monitor", "Sent already, no need to send again");
return;
}
if (notificationList.length > 0) {
let sent = false;
log.debug("monitor", "Send certificate notification");
let row = await R.getRow("SELECT * FROM notification_sent_history WHERE type = ? AND monitor_id = ? AND days = ?", [
for (let notification of notificationList) {
try {
log.debug("monitor", "Sending to " + notification.name);
await Notification.send(JSON.parse(notification.config), `[${this.name}][${this.url}] ${certType} certificate ${certCN} will be expired in ${daysRemaining} days`);
sent = true;
} catch (e) {
log.error("monitor", "Cannot send cert notification to " + notification.name);
log.error("monitor", e);
}
}
if (sent) {
await R.exec("INSERT INTO notification_sent_history (type, monitor_id, days) VALUES(?, ?, ?)", [
"certificate",
this.id,
targetDays,
]);
// Sent already, no need to send again
if (row) {
log.debug("monitor", "Sent already, no need to send again");
return;
}
let sent = false;
log.debug("monitor", "Send certificate notification");
for (let notification of notificationList) {
try {
log.debug("monitor", "Sending to " + notification.name);
await Notification.send(JSON.parse(notification.config), `[${this.name}][${this.url}] Certificate will be expired in ${daysRemaining} days`);
sent = true;
} catch (e) {
log.error("monitor", "Cannot send cert notification to " + notification.name);
log.error("monitor", e);
}
}
if (sent) {
await R.exec("INSERT INTO notification_sent_history (type, monitor_id, days) VALUES(?, ?, ?)", [
"certificate",
this.id,
targetDays,
]);
}
} else {
log.debug("monitor", "No notification, no need to send cert notification");
}
}
@@ -1291,18 +1361,24 @@ class Monitor extends BeanModel {
* @returns {Promise<boolean>}
*/
static async isUnderMaintenance(monitorID) {
let activeCondition = Maintenance.getActiveMaintenanceSQLCondition();
const maintenance = await R.getRow(`
SELECT COUNT(*) AS count
FROM monitor_maintenance mm
JOIN maintenance
ON mm.maintenance_id = maintenance.id
AND mm.monitor_id = ?
LEFT JOIN maintenance_timeslot
ON maintenance_timeslot.maintenance_id = maintenance.id
WHERE ${activeCondition}
LIMIT 1`, [ monitorID ]);
return maintenance.count !== 0;
const maintenanceIDList = await R.getCol(`
SELECT maintenance_id FROM monitor_maintenance
WHERE monitor_id = ?
`, [ monitorID ]);
for (const maintenanceID of maintenanceIDList) {
const maintenance = await UptimeKumaServer.getInstance().getMaintenance(maintenanceID);
if (maintenance && await maintenance.isUnderMaintenance()) {
return true;
}
}
const parent = await Monitor.getParent(monitorID);
if (parent != null) {
return await Monitor.isUnderMaintenance(parent.id);
}
return false;
}
/** Make sure monitor interval is between bounds */
@@ -1314,6 +1390,105 @@ class Monitor extends BeanModel {
throw new Error(`Interval cannot be less than ${MIN_INTERVAL_SECOND} seconds`);
}
}
/**
* Gets Parent of the monitor
* @param {number} monitorID ID of monitor to get
* @returns {Promise<LooseObject<any>>}
*/
static async getParent(monitorID) {
return await R.getRow(`
SELECT parent.* FROM monitor parent
LEFT JOIN monitor child
ON child.parent = parent.id
WHERE child.id = ?
`, [
monitorID,
]);
}
/**
* Gets all Children of the monitor
* @param {number} monitorID ID of monitor to get
* @returns {Promise<LooseObject<any>>}
*/
static async getChildren(monitorID) {
return await R.getAll(`
SELECT * FROM monitor
WHERE parent = ?
`, [
monitorID,
]);
}
/**
* Gets Full Path-Name (Groups and Name)
* @returns {Promise<String>}
*/
async getPathName() {
let path = this.name;
if (this.parent === null) {
return path;
}
let parent = await Monitor.getParent(this.id);
while (parent !== null) {
path = `${parent.name} / ${path}`;
parent = await Monitor.getParent(parent.id);
}
return path;
}
/**
* Gets recursive all child ids
* @param {number} monitorID ID of the monitor to get
* @returns {Promise<Array>}
*/
static async getAllChildrenIDs(monitorID) {
const childs = await Monitor.getChildren(monitorID);
if (childs === null) {
return [];
}
let childrenIDs = [];
for (const child of childs) {
childrenIDs.push(child.id);
childrenIDs = childrenIDs.concat(await Monitor.getAllChildrenIDs(child.id));
}
return childrenIDs;
}
/**
* Unlinks all children of the the group monitor
* @param {number} groupID ID of group to remove children of
* @returns {Promise<void>}
*/
static async unlinkAllChildren(groupID) {
return await R.exec("UPDATE `monitor` SET parent = ? WHERE parent = ? ", [
null, groupID
]);
}
/**
* Checks recursive if parent (ancestors) are active
* @param {number} monitorID ID of the monitor to get
* @returns {Promise<Boolean>}
*/
static async isParentActive(monitorID) {
const parent = await Monitor.getParent(monitorID);
if (parent === null) {
return true;
}
const parentActive = await Monitor.isParentActive(parent.id);
return parent.active && parentActive;
}
}
module.exports = Monitor;

View File

@@ -3,7 +3,6 @@ const { R } = require("redbean-node");
const cheerio = require("cheerio");
const { UptimeKumaServer } = require("../uptime-kuma-server");
const jsesc = require("jsesc");
const Maintenance = require("./maintenance");
const googleAnalytics = require("../google-analytics");
class StatusPage extends BeanModel {
@@ -290,21 +289,17 @@ class StatusPage extends BeanModel {
try {
const publicMaintenanceList = [];
let activeCondition = Maintenance.getActiveMaintenanceSQLCondition();
let maintenanceBeanList = R.convertToBeans("maintenance", await R.getAll(`
SELECT DISTINCT maintenance.*
FROM maintenance
JOIN maintenance_status_page
ON maintenance_status_page.maintenance_id = maintenance.id
AND maintenance_status_page.status_page_id = ?
LEFT JOIN maintenance_timeslot
ON maintenance_timeslot.maintenance_id = maintenance.id
WHERE ${activeCondition}
ORDER BY maintenance.end_date
`, [ statusPageId ]));
let maintenanceIDList = await R.getCol(`
SELECT DISTINCT maintenance_id
FROM maintenance_status_page
WHERE status_page_id = ?
`, [ statusPageId ]);
for (const bean of maintenanceBeanList) {
publicMaintenanceList.push(await bean.toPublicJSON());
for (const maintenanceID of maintenanceIDList) {
let maintenance = UptimeKumaServer.getInstance().getMaintenance(maintenanceID);
if (maintenance && await maintenance.isUnderMaintenance()) {
publicMaintenanceList.push(await maintenance.toPublicJSON());
}
}
return publicMaintenanceList;

View File

@@ -15,7 +15,7 @@ class DingDing extends NotificationProvider {
msgtype: "markdown",
markdown: {
title: `[${this.statusToString(heartbeatJSON["status"])}] ${monitorJSON["name"]}`,
text: `## [${this.statusToString(heartbeatJSON["status"])}] ${monitorJSON["name"]} \n > ${heartbeatJSON["msg"]} \n > Time(UTC):${heartbeatJSON["time"]}`,
text: `## [${this.statusToString(heartbeatJSON["status"])}] ${monitorJSON["name"]} \n> ${heartbeatJSON["msg"]}\n> Time (${heartbeatJSON["timezone"]}): ${heartbeatJSON["localDateTime"]}`,
}
};
if (this.sendToDingDing(notification, params)) {

View File

@@ -59,8 +59,8 @@ class Discord extends NotificationProvider {
value: monitorJSON["type"] === "push" ? "Heartbeat" : address,
},
{
name: "Time (UTC)",
value: heartbeatJSON["time"],
name: `Time (${heartbeatJSON["timezone"]})`,
value: heartbeatJSON["localDateTime"],
},
{
name: "Error",
@@ -94,8 +94,8 @@ class Discord extends NotificationProvider {
value: monitorJSON["type"] === "push" ? "Heartbeat" : address,
},
{
name: "Time (UTC)",
value: heartbeatJSON["time"],
name: `Time (${heartbeatJSON["timezone"]})`,
value: heartbeatJSON["localDateTime"],
},
{
name: "Ping",

View File

@@ -35,8 +35,7 @@ class Feishu extends NotificationProvider {
text:
"[Down] " +
heartbeatJSON["msg"] +
"\nTime (UTC): " +
heartbeatJSON["time"],
`\nTime (${heartbeatJSON["timezone"]}): ${heartbeatJSON["localDateTime"]}`
},
],
],
@@ -62,8 +61,7 @@ class Feishu extends NotificationProvider {
text:
"[Up] " +
heartbeatJSON["msg"] +
"\nTime (UTC): " +
heartbeatJSON["time"],
`\nTime (${heartbeatJSON["timezone"]}): ${heartbeatJSON["localDateTime"]}`,
},
],
],

View File

@@ -33,7 +33,10 @@ class Line extends NotificationProvider {
"messages": [
{
"type": "text",
"text": "UptimeKuma Alert: [🔴 Down]\n" + "Name: " + monitorJSON["name"] + " \n" + heartbeatJSON["msg"] + "\nTime (UTC): " + heartbeatJSON["time"]
"text": "UptimeKuma Alert: [🔴 Down]\n" +
"Name: " + monitorJSON["name"] + " \n" +
heartbeatJSON["msg"] +
`\nTime (${heartbeatJSON["timezone"]}): ${heartbeatJSON["localDateTime"]}`
}
]
};
@@ -44,7 +47,10 @@ class Line extends NotificationProvider {
"messages": [
{
"type": "text",
"text": "UptimeKuma Alert: [✅ Up]\n" + "Name: " + monitorJSON["name"] + " \n" + heartbeatJSON["msg"] + "\nTime (UTC): " + heartbeatJSON["time"]
"text": "UptimeKuma Alert: [✅ Up]\n" +
"Name: " + monitorJSON["name"] + " \n" +
heartbeatJSON["msg"] +
`\nTime (${heartbeatJSON["timezone"]}): ${heartbeatJSON["localDateTime"]}`
}
]
};

View File

@@ -24,12 +24,18 @@ class LineNotify extends NotificationProvider {
await axios.post(lineAPIUrl, qs.stringify(testMessage), config);
} else if (heartbeatJSON["status"] === DOWN) {
let downMessage = {
"message": "\n[🔴 Down]\n" + "Name: " + monitorJSON["name"] + " \n" + heartbeatJSON["msg"] + "\nTime (UTC): " + heartbeatJSON["time"]
"message": "\n[🔴 Down]\n" +
"Name: " + monitorJSON["name"] + " \n" +
heartbeatJSON["msg"] + "\n" +
`Time (${heartbeatJSON["timezone"]}): ${heartbeatJSON["localDateTime"]}`
};
await axios.post(lineAPIUrl, qs.stringify(downMessage), config);
} else if (heartbeatJSON["status"] === UP) {
let upMessage = {
"message": "\n[✅ Up]\n" + "Name: " + monitorJSON["name"] + " \n" + heartbeatJSON["msg"] + "\nTime (UTC): " + heartbeatJSON["time"]
"message": "\n[✅ Up]\n" +
"Name: " + monitorJSON["name"] + " \n" +
heartbeatJSON["msg"] + "\n" +
`Time (${heartbeatJSON["timezone"]}): ${heartbeatJSON["localDateTime"]}`
};
await axios.post(lineAPIUrl, qs.stringify(upMessage), config);
}

View File

@@ -28,7 +28,9 @@ class LunaSea extends NotificationProvider {
if (heartbeatJSON["status"] === DOWN) {
let downdata = {
"title": "UptimeKuma Alert: " + monitorJSON["name"],
"body": "[🔴 Down] " + heartbeatJSON["msg"] + "\nTime (UTC): " + heartbeatJSON["time"],
"body": "[🔴 Down] " +
heartbeatJSON["msg"] +
`\nTime (${heartbeatJSON["timezone"]}): ${heartbeatJSON["localDateTime"]}`
};
await axios.post(lunaseaurl, downdata);
return okMsg;
@@ -37,7 +39,9 @@ class LunaSea extends NotificationProvider {
if (heartbeatJSON["status"] === UP) {
let updata = {
"title": "UptimeKuma Alert: " + monitorJSON["name"],
"body": "[✅ Up] " + heartbeatJSON["msg"] + "\nTime (UTC): " + heartbeatJSON["time"],
"body": "[✅ Up] " +
heartbeatJSON["msg"] +
`\nTime (${heartbeatJSON["timezone"]}): ${heartbeatJSON["localDateTime"]}`
};
await axios.post(lunaseaurl, updata);
return okMsg;

View File

@@ -10,7 +10,7 @@ class Mattermost extends NotificationProvider {
let okMsg = "Sent Successfully.";
try {
const mattermostUserName = notification.mattermostusername || "Uptime Kuma";
// If heartbeatJSON is null, assume we're testing.
// If heartbeatJSON is null, assume non monitoring notification (Certificate warning) or testing.
if (heartbeatJSON == null) {
let mattermostTestData = {
username: mattermostUserName,
@@ -27,97 +27,79 @@ class Mattermost extends NotificationProvider {
}
const mattermostIconEmoji = notification.mattermosticonemo;
const mattermostIconUrl = notification.mattermosticonurl;
let mattermostIconEmojiOnline = "";
let mattermostIconEmojiOffline = "";
if (heartbeatJSON["status"] === DOWN) {
let mattermostdowndata = {
username: mattermostUserName,
text: "Uptime Kuma Alert",
channel: mattermostChannel,
icon_emoji: mattermostIconEmoji,
icon_url: mattermostIconUrl,
attachments: [
{
fallback:
"Your " +
monitorJSON["name"] +
" service went down.",
color: "#FF0000",
title:
"❌ " +
monitorJSON["name"] +
" service went down. ❌",
title_link: monitorJSON["url"],
fields: [
{
short: true,
title: "Service Name",
value: monitorJSON["name"],
},
{
short: true,
title: "Time (UTC)",
value: heartbeatJSON["time"],
},
{
short: false,
title: "Error",
value: heartbeatJSON["msg"],
},
],
},
],
};
await axios.post(
notification.mattermostWebhookUrl,
mattermostdowndata
);
return okMsg;
} else if (heartbeatJSON["status"] === UP) {
let mattermostupdata = {
username: mattermostUserName,
text: "Uptime Kuma Alert",
channel: mattermostChannel,
icon_emoji: mattermostIconEmoji,
icon_url: mattermostIconUrl,
attachments: [
{
fallback:
"Your " +
monitorJSON["name"] +
" service went up!",
color: "#32CD32",
title:
"✅ " +
monitorJSON["name"] +
" service went up! ✅",
title_link: monitorJSON["url"],
fields: [
{
short: true,
title: "Service Name",
value: monitorJSON["name"],
},
{
short: true,
title: "Time (UTC)",
value: heartbeatJSON["time"],
},
{
short: false,
title: "Ping",
value: heartbeatJSON["ping"] + "ms",
},
],
},
],
};
await axios.post(
notification.mattermostWebhookUrl,
mattermostupdata
);
return okMsg;
if (mattermostIconEmoji && typeof mattermostIconEmoji === "string") {
const emojiArray = mattermostIconEmoji.split(" ");
if (emojiArray.length >= 2) {
mattermostIconEmojiOnline = emojiArray[0];
mattermostIconEmojiOffline = emojiArray[1];
}
}
const mattermostIconUrl = notification.mattermosticonurl;
let iconEmoji = mattermostIconEmoji;
let statusField = {
short: false,
title: "Error",
value: heartbeatJSON.msg,
};
let statusText = "unknown";
let color = "#000000";
if (heartbeatJSON.status === DOWN) {
iconEmoji = mattermostIconEmojiOffline || mattermostIconEmoji;
statusField = {
short: false,
title: "Error",
value: heartbeatJSON.msg,
};
statusText = "down.";
color = "#FF0000";
} else if (heartbeatJSON.status === UP) {
iconEmoji = mattermostIconEmojiOnline || mattermostIconEmoji;
statusField = {
short: false,
title: "Ping",
value: heartbeatJSON.ping + "ms",
};
statusText = "up!";
color = "#32CD32";
}
let mattermostdata = {
username: monitorJSON.name + " " + mattermostUserName,
channel: mattermostChannel,
icon_emoji: iconEmoji,
icon_url: mattermostIconUrl,
attachments: [
{
fallback:
"Your " +
monitorJSON.name +
" service went " +
statusText,
color: color,
title:
monitorJSON.name +
" service went " +
statusText,
title_link: monitorJSON.url,
fields: [
statusField,
{
short: true,
title: `Time (${heartbeatJSON["timezone"]})`,
value: heartbeatJSON.localDateTime,
},
],
},
],
};
await axios.post(
notification.mattermostWebhookUrl,
mattermostdata
);
return okMsg;
} catch (error) {
this.throwGeneralAxiosError(error);
}

View File

@@ -1,5 +1,6 @@
const NotificationProvider = require("./notification-provider");
const axios = require("axios");
const { DOWN, UP } = require("../../src/util");
class Ntfy extends NotificationProvider {
@@ -9,16 +10,54 @@ class Ntfy extends NotificationProvider {
let okMsg = "Sent Successfully.";
try {
let headers = {};
if (notification.ntfyusername) {
if (notification.ntfyAuthenticationMethod === "usernamePassword") {
headers = {
"Authorization": "Basic " + Buffer.from(notification.ntfyusername + ":" + notification.ntfypassword).toString("base64"),
};
} else if (notification.ntfyAuthenticationMethod === "accessToken") {
headers = {
"Authorization": "Bearer " + notification.ntfyaccesstoken,
};
}
// If heartbeatJSON is null, assume non monitoring notification (Certificate warning) or testing.
if (heartbeatJSON == null) {
let ntfyTestData = {
"topic": notification.ntfytopic,
"title": (monitorJSON?.name || notification.ntfytopic) + " [Uptime-Kuma]",
"message": msg,
"priority": notification.ntfyPriority,
"tags": [ "test_tube" ],
};
await axios.post(`${notification.ntfyserverurl}`, ntfyTestData, { headers: headers });
return okMsg;
}
let tags = [];
let status = "unknown";
let priority = notification.ntfyPriority || 4;
if ("status" in heartbeatJSON) {
if (heartbeatJSON.status === DOWN) {
tags = [ "red_circle" ];
status = "Down";
// if priority is not 5, increase priority for down alerts
priority = priority === 5 ? priority : priority + 1;
} else if (heartbeatJSON["status"] === UP) {
tags = [ "green_circle" ];
status = "Up";
}
}
let data = {
"topic": notification.ntfytopic,
"message": msg,
"priority": notification.ntfyPriority || 4,
"title": "Uptime-Kuma",
"message": heartbeatJSON.msg,
"priority": priority,
"title": monitorJSON.name + " " + status + " [Uptime-Kuma]",
"tags": tags,
"actions": [
{
"action": "view",
"label": "Open " + monitorJSON.name,
"url": monitorJSON.url,
}
]
};
if (notification.ntfyIcon) {

View File

@@ -0,0 +1,97 @@
const NotificationProvider = require("./notification-provider");
const axios = require("axios");
const { UP, DOWN } = require("../../src/util");
const opsgenieAlertsUrlEU = "https://api.eu.opsgenie.com/v2/alerts";
const opsgenieAlertsUrlUS = "https://api.opsgenie.com/v2/alerts";
let okMsg = "Sent Successfully.";
class Opsgenie extends NotificationProvider {
name = "Opsgenie";
/**
* @inheritdoc
*/
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
let opsgenieAlertsUrl;
let priority = (!notification.opsgeniePriority) ? 3 : notification.opsgeniePriority;
const textMsg = "Uptime Kuma Alert";
try {
switch (notification.opsgenieRegion) {
case "US":
opsgenieAlertsUrl = opsgenieAlertsUrlUS;
break;
case "EU":
opsgenieAlertsUrl = opsgenieAlertsUrlEU;
break;
default:
opsgenieAlertsUrl = opsgenieAlertsUrlUS;
}
if (heartbeatJSON == null) {
let notificationTestAlias = "uptime-kuma-notification-test";
let data = {
"message": msg,
"alias": notificationTestAlias,
"source": "Uptime Kuma",
"priority": "P5"
};
return this.post(notification, opsgenieAlertsUrl, data);
}
if (heartbeatJSON.status === DOWN) {
let data = {
"message": monitorJSON ? textMsg + `: ${monitorJSON.name}` : textMsg,
"alias": monitorJSON.name,
"description": msg,
"source": "Uptime Kuma",
"priority": `P${priority}`
};
return this.post(notification, opsgenieAlertsUrl, data);
}
if (heartbeatJSON.status === UP) {
let opsgenieAlertsCloseUrl = `${opsgenieAlertsUrl}/${encodeURIComponent(monitorJSON.name)}/close?identifierType=alias`;
let data = {
"source": "Uptime Kuma",
};
return this.post(notification, opsgenieAlertsCloseUrl, data);
}
} catch (error) {
this.throwGeneralAxiosError(error);
}
}
/**
*
* @param {BeanModel} notification
* @param {string} url Request url
* @param {Object} data Request body
* @returns {Promise<string>}
*/
async post(notification, url, data) {
let config = {
headers: {
"Content-Type": "application/json",
"Authorization": `GenieKey ${notification.opsgenieApiKey}`,
}
};
let res = await axios.post(url, data, config);
if (res.status == null) {
return "Opsgenie notification failed with invalid response!";
}
if (res.status < 200 || res.status >= 300) {
return `Opsgenie notification failed with status code ${res.status}`;
}
return okMsg;
}
}
module.exports = Opsgenie;

View File

@@ -29,14 +29,18 @@ class Pushbullet extends NotificationProvider {
let downData = {
"type": "note",
"title": "UptimeKuma Alert: " + monitorJSON["name"],
"body": "[🔴 Down] " + heartbeatJSON["msg"] + "\nTime (UTC): " + heartbeatJSON["time"],
"body": "[🔴 Down] " +
heartbeatJSON["msg"] +
`\nTime (${heartbeatJSON["timezone"]}): ${heartbeatJSON["localDateTime"]}`,
};
await axios.post(pushbulletUrl, downData, config);
} else if (heartbeatJSON["status"] === UP) {
let upData = {
"type": "note",
"title": "UptimeKuma Alert: " + monitorJSON["name"],
"body": "[✅ Up] " + heartbeatJSON["msg"] + "\nTime (UTC): " + heartbeatJSON["time"],
"body": "[✅ Up] " +
heartbeatJSON["msg"] +
`\nTime (${heartbeatJSON["timezone"]}): ${heartbeatJSON["localDateTime"]}`,
};
await axios.post(pushbulletUrl, upData, config);
}

View File

@@ -24,13 +24,16 @@ class Pushover extends NotificationProvider {
if (notification.pushoverdevice) {
data.device = notification.pushoverdevice;
}
if (notification.pushoverttl) {
data.ttl = notification.pushoverttl;
}
try {
if (heartbeatJSON == null) {
await axios.post(pushoverlink, data);
return okMsg;
} else {
data.message += "\n<b>Time (UTC)</b>:" + heartbeatJSON["time"];
data.message += `\n<b>Time (${heartbeatJSON["timezone"]})</b>:${heartbeatJSON["localDateTime"]}`;
await axios.post(pushoverlink, data);
return okMsg;
}

View File

@@ -22,8 +22,6 @@ class RocketChat extends NotificationProvider {
return okMsg;
}
const time = heartbeatJSON["time"];
let data = {
"text": "Uptime Kuma Alert",
"channel": notification.rocketchannel,
@@ -31,7 +29,7 @@ class RocketChat extends NotificationProvider {
"icon_emoji": notification.rocketiconemo,
"attachments": [
{
"title": "Uptime Kuma Alert *Time (UTC)*\n" + time,
"title": `Uptime Kuma Alert *Time (${heartbeatJSON["timezone"]})*\n${heartbeatJSON["localDateTime"]}`,
"text": "*Message*\n" + msg,
}
]

View File

@@ -39,7 +39,6 @@ class Slack extends NotificationProvider {
return okMsg;
}
const time = heartbeatJSON["time"];
const textMsg = "Uptime Kuma Alert";
let data = {
"text": `${textMsg}\n${msg}`,
@@ -65,7 +64,7 @@ class Slack extends NotificationProvider {
},
{
"type": "mrkdwn",
"text": "*Time (UTC)*\n" + time,
"text": `*Time (${heartbeatJSON["timezone"]})*\n${heartbeatJSON["localDateTime"]}`,
}],
}
],

View File

@@ -91,7 +91,7 @@ class SMTP extends NotificationProvider {
let bodyTextContent = msg;
if (heartbeatJSON) {
bodyTextContent = `${msg}\nTime (UTC): ${heartbeatJSON["time"]}`;
bodyTextContent = `${msg}\nTime (${heartbeatJSON["timezone"]}): ${heartbeatJSON["localDateTime"]}`;
}
// send mail with defined transport object

View File

@@ -25,8 +25,11 @@ class Telegram extends NotificationProvider {
return okMsg;
} catch (error) {
let msg = (error.response.data.description) ? error.response.data.description : "Error without description";
throw new Error(msg);
if (error.response && error.response.data && error.response.data.description) {
throw new Error(error.response.data.description);
} else {
throw new Error(error.message);
}
}
}
}

View File

@@ -0,0 +1,41 @@
const NotificationProvider = require("./notification-provider");
const axios = require("axios");
class Twilio extends NotificationProvider {
name = "twilio";
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
let okMsg = "Sent Successfully.";
let accountSID = notification.twilioAccountSID;
let authToken = notification.twilioAuthToken;
try {
let config = {
headers: {
"Content-Type": "application/x-www-form-urlencoded;charset=utf-8",
"Authorization": "Basic " + Buffer.from(accountSID + ":" + authToken).toString("base64"),
}
};
let data = new URLSearchParams();
data.append("To", notification.twilioToNumber);
data.append("From", notification.twilioFromNumber);
data.append("Body", msg);
let url = "https://api.twilio.com/2010-04-01/Accounts/" + accountSID + "/Messages.json";
await axios.post(url, data, config);
return okMsg;
} catch (error) {
this.throwGeneralAxiosError(error);
}
}
}
module.exports = Twilio;

View File

@@ -23,6 +23,7 @@ const Mattermost = require("./notification-providers/mattermost");
const Ntfy = require("./notification-providers/ntfy");
const Octopush = require("./notification-providers/octopush");
const OneBot = require("./notification-providers/onebot");
const Opsgenie = require("./notification-providers/opsgenie");
const PagerDuty = require("./notification-providers/pagerduty");
const PagerTree = require("./notification-providers/pagertree");
const PromoSMS = require("./notification-providers/promosms");
@@ -41,6 +42,7 @@ const Stackfield = require("./notification-providers/stackfield");
const Teams = require("./notification-providers/teams");
const TechulusPush = require("./notification-providers/techulus-push");
const Telegram = require("./notification-providers/telegram");
const Twilio = require("./notification-providers/twilio");
const Splunk = require("./notification-providers/splunk");
const Webhook = require("./notification-providers/webhook");
const WeCom = require("./notification-providers/wecom");
@@ -83,6 +85,7 @@ class Notification {
new Ntfy(),
new Octopush(),
new OneBot(),
new Opsgenie(),
new PagerDuty(),
new PagerTree(),
new PromoSMS(),
@@ -103,6 +106,7 @@ class Notification {
new Teams(),
new TechulusPush(),
new Telegram(),
new Twilio(),
new Splunk(),
new Webhook(),
new WeCom(),

View File

@@ -28,7 +28,7 @@ const monitorResponseTime = new PrometheusClient.Gauge({
const monitorStatus = new PrometheusClient.Gauge({
name: "monitor_status",
help: "Monitor Status (1 = UP, 0= DOWN)",
help: "Monitor Status (1 = UP, 0= DOWN, 2= PENDING, 3= MAINTENANCE)",
labelNames: commonLabels
});

View File

@@ -132,6 +132,9 @@ class Proxy {
...httpAgentOptions,
...httpsAgentOptions,
...proxyOptions,
tls: {
rejectUnauthorized: httpsAgentOptions.rejectUnauthorized,
},
});
httpAgent = agent;

View File

@@ -10,6 +10,7 @@ const { UptimeKumaServer } = require("../uptime-kuma-server");
const { UptimeCacheList } = require("../uptime-cache-list");
const { makeBadge } = require("badge-maker");
const { badgeConstants } = require("../config");
const { Prometheus } = require("../prometheus");
let router = express.Router();
@@ -37,7 +38,7 @@ router.get("/api/push/:pushToken", async (request, response) => {
let pushToken = request.params.pushToken;
let msg = request.query.msg || "OK";
let ping = request.query.ping || null;
let ping = parseInt(request.query.ping) || null;
let statusString = request.query.status || "up";
let status = (statusString === "up") ? UP : DOWN;
@@ -89,6 +90,7 @@ router.get("/api/push/:pushToken", async (request, response) => {
io.to(monitor.user_id).emit("heartbeat", bean.toJSON());
UptimeCacheList.clearCache(monitor.id);
Monitor.sendStats(io, monitor.id, monitor.user_id);
new Prometheus(monitor).update(bean, undefined);
response.json({
ok: true,
@@ -147,7 +149,11 @@ router.get("/api/badge/:id/status", cache("5 minutes"), async (request, response
const heartbeat = await Monitor.getPreviousHeartbeat(requestedMonitorId);
const state = overrideValue !== undefined ? overrideValue : heartbeat.status;
badgeValues.label = label ?? "Status";
if (label === undefined) {
badgeValues.label = "Status";
} else {
badgeValues.label = label;
}
switch (state) {
case DOWN:
badgeValues.color = downColor;
@@ -224,7 +230,7 @@ router.get("/api/badge/:id/uptime/:duration?", cache("5 minutes"), async (reques
);
// limit the displayed uptime percentage to four (two, when displayed as percent) decimal digits
const cleanUptime = parseFloat(uptime.toPrecision(4));
const cleanUptime = (uptime * 100).toPrecision(4);
// use a given, custom color or calculate one based on the uptime value
badgeValues.color = color ?? percentageToColor(uptime);
@@ -235,7 +241,7 @@ router.get("/api/badge/:id/uptime/:duration?", cache("5 minutes"), async (reques
labelPrefix,
label ?? `Uptime (${requestedDuration}${labelSuffix})`,
]);
badgeValues.message = filterAndJoin([ prefix, `${cleanUptime * 100}`, suffix ]);
badgeValues.message = filterAndJoin([ prefix, cleanUptime, suffix ]);
}
// build the SVG based on given values

View File

@@ -19,6 +19,11 @@ const nodeVersion = parseInt(process.versions.node.split(".")[0]);
const requiredVersion = 14;
console.log(`Your Node.js version: ${nodeVersion}`);
// See more: https://github.com/louislam/uptime-kuma/issues/3138
if (nodeVersion >= 20) {
console.warn("\x1b[31m%s\x1b[0m", "Warning: Uptime Kuma is currently not stable on Node.js >= 20, please use Node.js 18.");
}
if (nodeVersion < requiredVersion) {
console.error(`Error: Your Node.js version is not supported, please upgrade to Node.js >= ${requiredVersion}.`);
process.exit(-1);
@@ -671,6 +676,7 @@ let needSetup = false;
// Edit a monitor
socket.on("editMonitor", async (monitor, callback) => {
try {
let removeGroupChildren = false;
checkLogin(socket);
let bean = await R.findOne("monitor", " id = ? ", [ monitor.id ]);
@@ -679,8 +685,22 @@ let needSetup = false;
throw new Error("Permission denied.");
}
// Check if Parent is Descendant (would cause endless loop)
if (monitor.parent !== null) {
const childIDs = await Monitor.getAllChildrenIDs(monitor.id);
if (childIDs.includes(monitor.parent)) {
throw new Error("Invalid Monitor Group");
}
}
// Remove children if monitor type has changed (from group to non-group)
if (bean.type === "group" && monitor.type !== bean.type) {
removeGroupChildren = true;
}
bean.name = monitor.name;
bean.description = monitor.description;
bean.parent = monitor.parent;
bean.type = monitor.type;
bean.url = monitor.url;
bean.method = monitor.method;
@@ -688,6 +708,9 @@ let needSetup = false;
bean.headers = monitor.headers;
bean.basic_auth_user = monitor.basic_auth_user;
bean.basic_auth_pass = monitor.basic_auth_pass;
bean.tlsCa = monitor.tlsCa;
bean.tlsCert = monitor.tlsCert;
bean.tlsKey = monitor.tlsKey;
bean.interval = monitor.interval;
bean.retryInterval = monitor.retryInterval;
bean.resendInterval = monitor.resendInterval;
@@ -735,9 +758,13 @@ let needSetup = false;
await R.store(bean);
if (removeGroupChildren) {
await Monitor.unlinkAllChildren(monitor.id);
}
await updateMonitorNotification(bean.id, monitor.notificationIDList);
if (bean.active) {
if (bean.isActive()) {
await restartMonitor(socket.userID, bean.id);
}
@@ -1554,7 +1581,7 @@ let needSetup = false;
}
});
initBackgroundJobs(args);
await initBackgroundJobs();
// Start cloudflared at the end if configured
await cloudflaredAutoStart(cloudflaredToken);

View File

@@ -5,7 +5,6 @@ const apicache = require("../modules/apicache");
const { UptimeKumaServer } = require("../uptime-kuma-server");
const Maintenance = require("../model/maintenance");
const server = UptimeKumaServer.getInstance();
const MaintenanceTimeslot = require("../model/maintenance_timeslot");
/**
* Handlers for Maintenance
@@ -19,10 +18,12 @@ module.exports.maintenanceSocketHandler = (socket) => {
log.debug("maintenance", maintenance);
let bean = Maintenance.jsonToBean(R.dispense("maintenance"), maintenance);
let bean = await Maintenance.jsonToBean(R.dispense("maintenance"), maintenance);
bean.user_id = socket.userID;
let maintenanceID = await R.store(bean);
await MaintenanceTimeslot.generateTimeslot(bean);
server.maintenanceList[maintenanceID] = bean;
await bean.run(true);
await server.sendMaintenanceList(socket);
@@ -45,17 +46,15 @@ module.exports.maintenanceSocketHandler = (socket) => {
try {
checkLogin(socket);
let bean = await R.findOne("maintenance", " id = ? ", [ maintenance.id ]);
let bean = server.getMaintenance(maintenance.id);
if (bean.user_id !== socket.userID) {
throw new Error("Permission denied.");
}
Maintenance.jsonToBean(bean, maintenance);
await Maintenance.jsonToBean(bean, maintenance);
await R.store(bean);
await MaintenanceTimeslot.generateTimeslot(bean, null, true);
await bean.run(true);
await server.sendMaintenanceList(socket);
callback({
@@ -187,7 +186,7 @@ module.exports.maintenanceSocketHandler = (socket) => {
log.debug("maintenance", `Get Monitors for Maintenance: ${maintenanceID} User ID: ${socket.userID}`);
let monitors = await R.getAll("SELECT monitor.id, monitor.name FROM monitor_maintenance mm JOIN monitor ON mm.monitor_id = monitor.id WHERE mm.maintenance_id = ? ", [
let monitors = await R.getAll("SELECT monitor.id FROM monitor_maintenance mm JOIN monitor ON mm.monitor_id = monitor.id WHERE mm.maintenance_id = ? ", [
maintenanceID,
]);
@@ -236,6 +235,7 @@ module.exports.maintenanceSocketHandler = (socket) => {
log.debug("maintenance", `Delete Maintenance: ${maintenanceID} User ID: ${socket.userID}`);
if (maintenanceID in server.maintenanceList) {
server.maintenanceList[maintenanceID].stop();
delete server.maintenanceList[maintenanceID];
}
@@ -267,9 +267,15 @@ module.exports.maintenanceSocketHandler = (socket) => {
log.debug("maintenance", `Pause Maintenance: ${maintenanceID} User ID: ${socket.userID}`);
await R.exec("UPDATE maintenance SET active = 0 WHERE id = ? ", [
maintenanceID,
]);
let maintenance = server.getMaintenance(maintenanceID);
if (!maintenance) {
throw new Error("Maintenance not found");
}
maintenance.active = false;
await R.store(maintenance);
maintenance.stop();
apicache.clear();
@@ -294,9 +300,15 @@ module.exports.maintenanceSocketHandler = (socket) => {
log.debug("maintenance", `Resume Maintenance: ${maintenanceID} User ID: ${socket.userID}`);
await R.exec("UPDATE maintenance SET active = 1 WHERE id = ? ", [
maintenanceID,
]);
let maintenance = server.getMaintenance(maintenanceID);
if (!maintenance) {
throw new Error("Maintenance not found");
}
maintenance.active = true;
await R.store(maintenance);
await maintenance.run();
apicache.clear();

View File

@@ -276,7 +276,7 @@ module.exports.statusPageSocketHandler = (socket) => {
let statusPage = R.dispense("status_page");
statusPage.slug = slug;
statusPage.title = title;
statusPage.theme = "light";
statusPage.theme = "auto";
statusPage.icon = "";
await R.store(statusPage);

View File

@@ -47,8 +47,6 @@ class UptimeKumaServer {
*/
indexHTML = "";
generateMaintenanceTimeslotsInterval = undefined;
/**
* Plugins Manager
* @type {PluginsManager}
@@ -74,6 +72,7 @@ class UptimeKumaServer {
// SSL
const sslKey = args["ssl-key"] || process.env.UPTIME_KUMA_SSL_KEY || process.env.SSL_KEY || undefined;
const sslCert = args["ssl-cert"] || process.env.UPTIME_KUMA_SSL_CERT || process.env.SSL_CERT || undefined;
const sslKeyPassphrase = args["ssl-key-passphrase"] || process.env.UPTIME_KUMA_SSL_KEY_PASSPHRASE || process.env.SSL_KEY_PASSPHRASE || undefined;
log.info("server", "Creating express and socket.io instance");
this.app = express();
@@ -81,7 +80,8 @@ class UptimeKumaServer {
log.info("server", "Server Type: HTTPS");
this.httpServer = https.createServer({
key: fs.readFileSync(sslKey),
cert: fs.readFileSync(sslCert)
cert: fs.readFileSync(sslCert),
passphrase: sslKeyPassphrase,
}, this.app);
} else {
log.info("server", "Server Type: HTTP");
@@ -110,8 +110,7 @@ class UptimeKumaServer {
log.debug("DEBUG", "Timezone: " + process.env.TZ);
log.debug("DEBUG", "Current Time: " + dayjs.tz().format());
await this.generateMaintenanceTimeslots();
this.generateMaintenanceTimeslotsInterval = setInterval(this.generateMaintenanceTimeslots, 60 * 1000);
await this.loadMaintenanceList();
}
/**
@@ -173,16 +172,33 @@ class UptimeKumaServer {
*/
async getMaintenanceJSONList(userID) {
let result = {};
for (let maintenanceID in this.maintenanceList) {
result[maintenanceID] = await this.maintenanceList[maintenanceID].toJSON();
}
return result;
}
/**
* Load maintenance list and run
* @param userID
* @returns {Promise<void>}
*/
async loadMaintenanceList(userID) {
let maintenanceList = await R.findAll("maintenance", " ORDER BY end_date DESC, title", [
let maintenanceList = await R.find("maintenance", " user_id = ? ORDER BY end_date DESC, title", [
userID,
]);
for (let maintenance of maintenanceList) {
result[maintenance.id] = await maintenance.toJSON();
this.maintenanceList[maintenance.id] = maintenance;
maintenance.run(this);
}
}
return result;
getMaintenance(maintenanceID) {
if (this.maintenanceList[maintenanceID]) {
return this.maintenanceList[maintenanceID];
}
return null;
}
/**
@@ -238,7 +254,7 @@ class UptimeKumaServer {
* Attempt to get the current server timezone
* If this fails, fall back to environment variables and then make a
* guess.
* @returns {string}
* @returns {Promise<string>}
*/
async getTimezone() {
let timezone = await Settings.get("serverTimezone");
@@ -269,28 +285,9 @@ class UptimeKumaServer {
dayjs.tz.setDefault(timezone);
}
/** Load the timeslots for maintenance */
async generateMaintenanceTimeslots() {
log.debug("maintenance", "Routine: Generating Maintenance Timeslots");
// Prevent #2776
// Remove duplicate maintenance_timeslot with same start_date, end_date and maintenance_id
await R.exec("DELETE FROM maintenance_timeslot WHERE id NOT IN (SELECT MIN(id) FROM maintenance_timeslot GROUP BY start_date, end_date, maintenance_id)");
let list = await R.find("maintenance_timeslot", " generated_next = 0 AND start_date <= DATETIME('now') ");
for (let maintenanceTimeslot of list) {
let maintenance = await maintenanceTimeslot.maintenance;
await MaintenanceTimeslot.generateTimeslot(maintenance, maintenanceTimeslot.end_date, false);
maintenanceTimeslot.generated_next = true;
await R.store(maintenanceTimeslot);
}
}
/** Stop the server */
async stop() {
clearTimeout(this.generateMaintenanceTimeslotsInterval);
}
loadPlugins() {
@@ -339,5 +336,4 @@ module.exports = {
};
// Must be at the end
const MaintenanceTimeslot = require("./model/maintenance_timeslot");
const { MonitorType } = require("./monitor-types/monitor-type");

View File

@@ -322,21 +322,33 @@ exports.postgresQuery = function (connectionString, query) {
* Run a query on MySQL/MariaDB
* @param {string} connectionString The database connection string
* @param {string} query The query to validate the database with
* @returns {Promise<(string[]|Object[]|Object)>}
* @returns {Promise<(string)>}
*/
exports.mysqlQuery = function (connectionString, query) {
return new Promise((resolve, reject) => {
const connection = mysql.createConnection(connectionString);
connection.promise().query(query)
.then(res => {
resolve(res);
})
.catch(err => {
connection.on("error", (err) => {
reject(err);
});
connection.query(query, (err, res) => {
if (err) {
reject(err);
})
.finally(() => {
} else {
if (Array.isArray(res)) {
resolve("Rows: " + res.length);
} else {
resolve("No Error, but the result is not an array. Type: " + typeof res);
}
}
try {
connection.end();
} catch (_) {
connection.destroy();
});
}
});
});
};
@@ -512,12 +524,16 @@ const parseCertificateInfo = function (info) {
// Move up the chain until loop is encountered
if (link.issuerCertificate == null) {
link.certType = (i === 0) ? "self-signed" : "root CA";
break;
} else if (link.issuerCertificate.fingerprint in existingList) {
// a root CA certificate is typically "signed by itself" (=> "self signed certificate") and thus the "issuerCertificate" is a reference to itself.
log.debug("cert", `[Last] ${link.issuerCertificate.fingerprint}`);
link.certType = (i === 0) ? "self-signed" : "root CA";
link.issuerCertificate = null;
break;
} else {
link.certType = (i === 0) ? "server" : "intermediate CA";
link = link.issuerCertificate;
}

View File

@@ -266,6 +266,11 @@ optgroup {
background-color: $dark-bg2;
}
.form-select:disabled {
color: rgba($dark-font-color, 0.7);
background-color: $dark-bg;
}
.form-control, .form-select {
border-color: $dark-border-color;
}
@@ -556,6 +561,31 @@ h5.settings-subheading::after {
border-bottom: 1px solid $dark-border-color;
}
$shadow-box-padding: 20px;
.shadow-box-with-fixed-bottom-bar {
padding-top: $shadow-box-padding;
padding-bottom: 0;
padding-right: $shadow-box-padding;
padding-left: $shadow-box-padding;
}
.fixed-bottom-bar {
position: sticky;
bottom: 0;
margin-left: -$shadow-box-padding;
margin-right: -$shadow-box-padding;
z-index: 100;
background-color: rgba(white, 0.2);
backdrop-filter: blur(2px);
border-radius: 0 0 10px 10px;
.dark & {
background-color: rgba($dark-header-bg, 0.9);
}
}
// Localization
@import "localization.scss";

View File

@@ -1,6 +1,12 @@
@import "vars.scss";
@import "node_modules/vue-multiselect/dist/vue-multiselect";
.multiselect {
.dark & {
color: $dark-font-color;
}
}
.multiselect__tags {
border-radius: 1.5rem;
border: 1px solid #ced4da;
@@ -14,10 +20,12 @@
.multiselect__option--highlight {
background: $primary !important;
color: $dark-font-color2 !important;
}
.multiselect__option--highlight::after {
background: $primary !important;
color: $dark-font-color2 !important;
}
.multiselect__tag {

View File

@@ -48,15 +48,14 @@
</div>
</div>
</div>
<div class="modal-footer">
<button
id="monitor-submit-btn" class="btn btn-primary" type="submit"
:disabled="processing"
>
{{ $t("Generate") }}
</button>
</div>
</div>
<div class="modal-footer">
<button
id="monitor-submit-btn" class="btn btn-primary" type="submit"
:disabled="processing"
>
{{ $t("Generate") }}
</button>
</div>
</div>
</div>
@@ -159,6 +158,16 @@ export default {
}
});
},
/** Clear Form inputs */
clearForm() {
this.key = {
name: "",
expires: this.minDate,
active: 1,
};
this.noExpire = false;
},
}
};
</script>

View File

@@ -0,0 +1,299 @@
<template>
<div ref="BadgeGeneratorModal" class="modal fade" tabindex="-1" data-bs-backdrop="static">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">
{{ $t("Badge Generator", [monitor.name]) }}
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close" />
</div>
<div class="modal-body">
<div class="mb-3">
<label for="type" class="form-label">{{ $t("Badge Type") }}</label>
<select id="type" v-model="badge.type" class="form-select">
<option value="status">status</option>
<option value="uptime">uptime</option>
<option value="ping">ping</option>
<option value="avg-response">avg-response</option>
<option value="cert-exp">cert-exp</option>
<option value="response">response</option>
</select>
</div>
<div v-if=" (parameters[badge.type || 'null'] || [] ).includes('duration') " class="mb-3">
<label for="duration" class="form-label">{{ $t("Badge Duration") }}</label>
<input id="duration" v-model="badge.duration" type="number" min="0" class="form-control" required>
</div>
<div v-if=" (parameters[badge.type || 'null'] || [] ).includes('label') " class="mb-3">
<label for="label" class="form-label">{{ $t("Badge Label") }}</label>
<input id="label" v-model="badge.label" type="text" class="form-control" required>
</div>
<div v-if=" (parameters[badge.type || 'null'] || [] ).includes('prefix') " class="mb-3">
<label for="prefix" class="form-label">{{ $t("Badge Prefix") }}</label>
<input id="prefix" v-model="badge.prefix" type="text" class="form-control" required>
</div>
<div v-if=" (parameters[badge.type || 'null'] || [] ).includes('suffix') " class="mb-3">
<label for="suffix" class="form-label">{{ $t("Badge Suffix") }}</label>
<input id="suffix" v-model="badge.suffix" type="text" class="form-control" required>
</div>
<div v-if=" (parameters[badge.type || 'null'] || [] ).includes('labelColor') " class="mb-3">
<label for="labelColor" class="form-label">{{ $t("Badge Label Color") }}</label>
<input id="labelColor" v-model="badge.labelColor" type="text" class="form-control" required>
</div>
<div v-if=" (parameters[badge.type || 'null'] || [] ).includes('color') " class="mb-3">
<label for="color" class="form-label">{{ $t("Badge Color") }}</label>
<input id="color" v-model="badge.color" type="text" class="form-control" required>
</div>
<div v-if=" (parameters[badge.type || 'null'] || [] ).includes('labelPrefix') " class="mb-3">
<label for="labelPrefix" class="form-label">{{ $t("Badge Label Prefix") }}</label>
<input id="labelPrefix" v-model="badge.labelPrefix" type="text" class="form-control" required>
</div>
<div v-if=" (parameters[badge.type || 'null'] || [] ).includes('labelSuffix') " class="mb-3">
<label for="labelSuffix" class="form-label">{{ $t("Badge Label Suffix") }}</label>
<input id="labelSuffix" v-model="badge.labelSuffix" type="text" class="form-control" required>
</div>
<div v-if=" (parameters[badge.type || 'null'] || [] ).includes('upColor') " class="mb-3">
<label for="upColor" class="form-label">{{ $t("Badge Up Color") }}</label>
<input id="upColor" v-model="badge.upColor" type="text" class="form-control" required>
</div>
<div v-if=" (parameters[badge.type || 'null'] || [] ).includes('downColor') " class="mb-3">
<label for="downColor" class="form-label">{{ $t("Badge Down Color") }}</label>
<input id="downColor" v-model="badge.downColor" type="text" class="form-control" required>
</div>
<div v-if=" (parameters[badge.type || 'null'] || [] ).includes('pendingColor') " class="mb-3">
<label for="pendingColor" class="form-label">{{ $t("Badge Pending Color") }}</label>
<input id="pendingColor" v-model="badge.pendingColor" type="text" class="form-control" required>
</div>
<div v-if=" (parameters[badge.type || 'null'] || [] ).includes('maintenanceColor') " class="mb-3">
<label for="maintenanceColor" class="form-label">{{ $t("Badge Maintenance Color") }}</label>
<input id="maintenanceColor" v-model="badge.maintenanceColor" type="text" class="form-control" required>
</div>
<div v-if=" (parameters[badge.type || 'null'] || [] ).includes('warnColor') " class="mb-3">
<label for="warnColor" class="form-label">{{ $t("Badge Warn Color") }}</label>
<input id="warnColor" v-model="badge.warnColor" type="number" min="0" class="form-control" required>
</div>
<div v-if=" (parameters[badge.type || 'null'] || [] ).includes('warnDays') " class="mb-3">
<label for="warnDays" class="form-label">{{ $t("Badge Warn Days") }}</label>
<input id="warnDays" v-model="badge.warnDays" type="number" min="0" class="form-control" required>
</div>
<div v-if=" (parameters[badge.type || 'null'] || [] ).includes('downDays') " class="mb-3">
<label for="downDays" class="form-label">{{ $t("Badge Down Days") }}</label>
<input id="downDays" v-model="badge.downDays" type="number" min="0" class="form-control" required>
</div>
<div class="mb-3">
<label for="style" class="form-label">{{ $t("Badge Style") }}</label>
<select id="style" v-model="badge.style" class="form-select">
<option value="plastic">plastic</option>
<option value="flat">flat</option>
<option value="flat-square">flat-square</option>
<option value="for-the-badge">for-the-badge</option>
<option value="social">social</option>
</select>
</div>
<div class="mb-3">
<label for="value" class="form-label">{{ $t("Badge value (For Testing only.)") }}</label>
<input id="value" v-model="badge.value" type="text" class="form-control" required>
</div>
<div class="my-3">
<label for="push-url" class="form-label">{{ $t("Badge URL") }}</label>
<CopyableInput id="push-url" v-model="badgeURL" type="url" disabled="disabled" />
</div>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-danger" data-bs-dismiss="modal">
{{ $t("Close") }}
</button>
</div>
</div>
</div>
</div>
</template>
<script lang="ts">
import { Modal } from "bootstrap";
import CopyableInput from "./CopyableInput.vue";
export default {
components: {
CopyableInput
},
props: {},
emits: [],
data() {
return {
model: null,
processing: false,
monitor: {
id: null,
name: null,
},
badge: {
type: "status",
duration: null,
label: null,
prefix: null,
suffix: null,
labelColor: null,
color: null,
labelPrefix: null,
labelSuffix: null,
upColor: null,
downColor: null,
pendingColor: null,
maintenanceColor: null,
warnColor: null,
warnDays: null,
downDays: null,
style: "flat",
value: null,
},
parameters: {
status: [
"upLabel",
"downLabel",
"pendingLabel",
"maintenanceLabel",
"upColor",
"downColor",
"pendingColor",
"maintenanceColor",
],
uptime: [
"duration",
"labelPrefix",
"labelSuffix",
"prefix",
"suffix",
"color",
"labelColor",
],
ping: [
"duration",
"labelPrefix",
"labelSuffix",
"prefix",
"suffix",
"color",
"labelColor",
],
"avg-response": [
"duration",
"labelPrefix",
"labelSuffix",
"prefix",
"suffix",
"color",
"labelColor",
],
"cert-exp": [
"labelPrefix",
"labelSuffix",
"prefix",
"suffix",
"upColor",
"warnColor",
"downColor",
"warnDays",
"downDays",
"labelColor",
],
response: [
"labelPrefix",
"labelSuffix",
"prefix",
"suffix",
"color",
"labelColor",
],
}
};
},
computed: {
badgeURL() {
if (!this.monitor.id || !this.badge.type) {
return;
}
let badgeURL = this.$root.baseURL + "/api/badge/" + this.monitor.id + "/" + this.badge.type;
let parameterList = {};
for (let parameter of this.parameters[this.badge.type] || []) {
if (parameter === "duration" && this.badge.duration) {
badgeURL += "/" + this.badge.duration;
continue;
}
if (this.badge[parameter]) {
parameterList[parameter] = this.badge[parameter];
}
}
for (let parameter of [ "label", "style", "value" ]) {
if (parameter === "style" && this.badge.style === "flat") {
continue;
}
if (this.badge[parameter]) {
parameterList[parameter] = this.badge[parameter];
}
}
if (Object.keys(parameterList).length > 0) {
return badgeURL + "?" + new URLSearchParams(parameterList);
}
return badgeURL;
},
},
mounted() {
this.BadgeGeneratorModal = new Modal(this.$refs.BadgeGeneratorModal);
},
methods: {
/**
* Setting monitor
* @param {number} monitorId ID of monitor
* @param {string} monitorName Name of monitor
*/
show(monitorId, monitorName) {
this.monitor = {
id: monitorId,
name: monitorName,
};
this.BadgeGeneratorModal.show();
},
},
};
</script>
<style lang="scss" scoped>
@import "../assets/vars.scss";
.dark {
.modal-dialog .form-text, .modal-dialog p {
color: $dark-font-color;
}
}
</style>

View File

@@ -13,6 +13,9 @@
:disabled="disabled"
>
<!-- A hidden textarea for copying text on non-https -->
<textarea ref="hiddenTextarea" style="position: fixed; left: -999999px; top: -999999px;"></textarea>
<a class="btn btn-outline-primary" @click="copyToClipboard(model)">
<font-awesome-icon :icon="icon" />
</a>
@@ -111,24 +114,19 @@ export default {
}, 3000);
// navigator clipboard api needs a secure context (https)
// For http, use the text area method (else part)
if (navigator.clipboard && window.isSecureContext) {
// navigator clipboard api method'
return navigator.clipboard.writeText(textToCopy);
} else {
// text area method
let textArea = document.createElement("textarea");
let textArea = this.$refs.hiddenTextarea;
textArea.value = textToCopy;
// make the textarea out of viewport
textArea.style.position = "fixed";
textArea.style.left = "-999999px";
textArea.style.top = "-999999px";
document.body.appendChild(textArea);
textArea.focus();
textArea.select();
return new Promise((res, rej) => {
// here the magic happens
document.execCommand("copy") ? res() : rej();
textArea.remove();
});
}
}

View File

@@ -3,16 +3,23 @@
<div v-if="maintenance.strategy === 'manual'" class="timeslot">
{{ $t("Manual") }}
</div>
<div v-else-if="maintenance.timeslotList.length > 0" class="timeslot">
{{ maintenance.timeslotList[0].startDateServerTimezone }}
<span class="to">-</span>
{{ maintenance.timeslotList[0].endDateServerTimezone }}
(UTC{{ maintenance.timeslotList[0].serverTimezoneOffset }})
<div v-else-if="maintenance.timeslotList.length > 0">
<div class="timeslot">
{{ startDateTime }}
<span class="to">-</span>
{{ endDateTime }}
</div>
<div class="timeslot">
UTC{{ maintenance.timezoneOffset }} <span v-if="maintenance.timezone !== 'UTC'">{{ maintenance.timezone }}</span>
</div>
</div>
</div>
</template>
<script>
import dayjs from "dayjs";
import { SQL_DATETIME_FORMAT_WITHOUT_SECOND } from "../util.ts";
export default {
props: {
maintenance: {
@@ -20,6 +27,14 @@ export default {
required: true
},
},
computed: {
startDateTime() {
return dayjs(this.maintenance.timeslotList[0].startDate).tz(this.maintenance.timezone).format(SQL_DATETIME_FORMAT_WITHOUT_SECOND);
},
endDateTime() {
return dayjs(this.maintenance.timeslotList[0].endDate).tz(this.maintenance.timezone).format(SQL_DATETIME_FORMAT_WITHOUT_SECOND);
}
},
};
</script>
@@ -31,6 +46,7 @@ export default {
background-color: rgba(255, 255, 255, 0.5);
border-radius: 20px;
padding: 0 10px;
margin-right: 5px;
.to {
margin: 0 6px;

View File

@@ -19,43 +19,18 @@
{{ $t("No Monitors, please") }} <router-link to="/add">{{ $t("add one") }}</router-link>
</div>
<router-link v-for="(item, index) in sortedMonitorList" :key="index" :to="monitorURL(item.id)" class="item" :class="{ 'disabled': ! item.active }" :title="item.description">
<div class="row">
<div class="col-9 col-md-8 small-padding" :class="{ 'monitor-item': $root.userHeartbeatBar == 'bottom' || $root.userHeartbeatBar == 'none' }">
<div class="info">
<Uptime :monitor="item" type="24" :pill="true" />
{{ item.name }}
</div>
<div class="tags">
<Tag v-for="tag in item.tags" :key="tag" :item="tag" :size="'sm'" />
</div>
</div>
<div v-show="$root.userHeartbeatBar == 'normal'" :key="$root.userHeartbeatBar" class="col-3 col-md-4">
<HeartbeatBar size="small" :monitor-id="item.id" />
</div>
</div>
<div v-if="$root.userHeartbeatBar == 'bottom'" class="row">
<div class="col-12 bottom-style">
<HeartbeatBar size="small" :monitor-id="item.id" />
</div>
</div>
</router-link>
<MonitorListItem v-for="(item, index) in sortedMonitorList" :key="index" :monitor="item" :isSearch="searchText !== ''" />
</div>
</div>
</template>
<script>
import HeartbeatBar from "../components/HeartbeatBar.vue";
import Tag from "../components/Tag.vue";
import Uptime from "../components/Uptime.vue";
import MonitorListItem from "../components/MonitorListItem.vue";
import { getMonitorRelativeURL } from "../util.ts";
export default {
components: {
Uptime,
HeartbeatBar,
Tag,
MonitorListItem,
},
props: {
/** Should the scrollbar be shown */
@@ -91,6 +66,20 @@ export default {
sortedMonitorList() {
let result = Object.values(this.$root.monitorList);
// Simple filter by search text
// finds monitor name, tag name or tag value
if (this.searchText !== "") {
const loweredSearchText = this.searchText.toLowerCase();
result = result.filter(monitor => {
return monitor.name.toLowerCase().includes(loweredSearchText)
|| monitor.tags.find(tag => tag.name.toLowerCase().includes(loweredSearchText)
|| tag.value?.toLowerCase().includes(loweredSearchText));
});
} else {
result = result.filter(monitor => monitor.parent === null);
}
// Filter result by active state, weight and alphabetical
result.sort((m1, m2) => {
if (m1.active !== m2.active) {
@@ -116,17 +105,6 @@ export default {
return m1.name.localeCompare(m2.name);
});
// Simple filter by search text
// finds monitor name, tag name or tag value
if (this.searchText !== "") {
const loweredSearchText = this.searchText.toLowerCase();
result = result.filter(monitor => {
return monitor.name.toLowerCase().includes(loweredSearchText)
|| monitor.tags.find(tag => tag.name.toLowerCase().includes(loweredSearchText)
|| tag.value?.toLowerCase().includes(loweredSearchText));
});
}
return result;
},
},

View File

@@ -0,0 +1,204 @@
<template>
<div>
<router-link :to="monitorURL(monitor.id)" class="item" :class="{ 'disabled': ! monitor.active }">
<div class="row">
<div class="col-9 col-md-8 small-padding" :class="{ 'monitor-item': $root.userHeartbeatBar == 'bottom' || $root.userHeartbeatBar == 'none' }">
<div class="info" :style="depthMargin">
<Uptime :monitor="monitor" type="24" :pill="true" />
<span v-if="hasChildren" class="collapse-padding" @click.prevent="changeCollapsed">
<font-awesome-icon icon="chevron-down" class="animated" :class="{ collapsed: isCollapsed}" />
</span>
{{ monitorName }}
</div>
<div class="tags">
<Tag v-for="tag in monitor.tags" :key="tag" :item="tag" :size="'sm'" />
</div>
</div>
<div v-show="$root.userHeartbeatBar == 'normal'" :key="$root.userHeartbeatBar" class="col-3 col-md-4">
<HeartbeatBar size="small" :monitor-id="monitor.id" />
</div>
</div>
<div v-if="$root.userHeartbeatBar == 'bottom'" class="row">
<div class="col-12 bottom-style">
<HeartbeatBar size="small" :monitor-id="monitor.id" />
</div>
</div>
</router-link>
<transition name="slide-fade-up">
<div v-if="!isCollapsed" class="childs">
<MonitorListItem v-for="(item, index) in sortedChildMonitorList" :key="index" :monitor="item" :isSearch="isSearch" :depth="depth + 1" />
</div>
</transition>
</div>
</template>
<script>
import HeartbeatBar from "../components/HeartbeatBar.vue";
import Tag from "../components/Tag.vue";
import Uptime from "../components/Uptime.vue";
import { getMonitorRelativeURL } from "../util.ts";
export default {
name: "MonitorListItem",
components: {
Uptime,
HeartbeatBar,
Tag,
},
props: {
/** Monitor this represents */
monitor: {
type: Object,
default: null,
},
/** If the user is currently searching */
isSearch: {
type: Boolean,
default: false,
},
/** How many ancestors are above this monitor */
depth: {
type: Number,
default: 0,
},
},
data() {
return {
isCollapsed: true,
};
},
computed: {
sortedChildMonitorList() {
let result = Object.values(this.$root.monitorList);
result = result.filter(childMonitor => childMonitor.parent === this.monitor.id);
result.sort((m1, m2) => {
if (m1.active !== m2.active) {
if (m1.active === 0) {
return 1;
}
if (m2.active === 0) {
return -1;
}
}
if (m1.weight !== m2.weight) {
if (m1.weight > m2.weight) {
return -1;
}
if (m1.weight < m2.weight) {
return 1;
}
}
return m1.name.localeCompare(m2.name);
});
return result;
},
hasChildren() {
return this.sortedChildMonitorList.length > 0;
},
depthMargin() {
return {
marginLeft: `${31 * this.depth}px`,
};
},
monitorName() {
if (this.isSearch) {
return this.monitor.pathName;
} else {
return this.monitor.name;
}
}
},
beforeMount() {
// Always unfold if monitor is accessed directly
if (this.monitor.childrenIDs.includes(parseInt(this.$route.params.id))) {
this.isCollapsed = false;
return;
}
// Set collapsed value based on local storage
let storage = window.localStorage.getItem("monitorCollapsed");
if (storage === null) {
return;
}
let storageObject = JSON.parse(storage);
if (storageObject[`monitor_${this.monitor.id}`] == null) {
return;
}
this.isCollapsed = storageObject[`monitor_${this.monitor.id}`];
},
methods: {
/**
* Changes the collapsed value of the current monitor and saves it to local storage
*/
changeCollapsed() {
this.isCollapsed = !this.isCollapsed;
// Save collapsed value into local storage
let storage = window.localStorage.getItem("monitorCollapsed");
let storageObject = {};
if (storage !== null) {
storageObject = JSON.parse(storage);
}
storageObject[`monitor_${this.monitor.id}`] = this.isCollapsed;
window.localStorage.setItem("monitorCollapsed", JSON.stringify(storageObject));
},
/**
* Get URL of monitor
* @param {number} id ID of monitor
* @returns {string} Relative URL of monitor
*/
monitorURL(id) {
return getMonitorRelativeURL(id);
},
},
};
</script>
<style lang="scss" scoped>
@import "../assets/vars.scss";
.small-padding {
padding-left: 5px !important;
padding-right: 5px !important;
}
.collapse-padding {
padding-left: 8px !important;
padding-right: 2px !important;
}
// .monitor-item {
// width: 100%;
// }
.tags {
margin-top: 4px;
padding-left: 67px;
display: flex;
flex-wrap: wrap;
gap: 0;
}
.collapsed {
transform: rotate(-90deg);
}
.animated {
transition: all 0.2s $easing-in;
}
</style>

View File

@@ -0,0 +1,123 @@
<template>
<div ref="MonitorSettingDialog" class="modal fade" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">
{{ $t("Monitor Setting", [monitor.name]) }}
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close" />
</div>
<div class="modal-body">
<div class="my-3 form-check">
<input id="show-clickable-link" v-model="monitor.isClickAble" class="form-check-input" type="checkbox" @click="toggleLink(monitor.group_index, monitor.monitor_index)" />
<label class="form-check-label" for="show-clickable-link">
{{ $t("Show Clickable Link") }}
</label>
<div class="form-text">
{{ $t("Show Clickable Link Description") }}
</div>
</div>
<button
class="btn btn-primary btn-add-group me-2"
@click="$refs.badgeGeneratorDialog.show(monitor.id, monitor.name)"
>
<font-awesome-icon icon="certificate" />
{{ $t("Open Badge Generator") }}
</button>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-danger" data-bs-dismiss="modal">
{{ $t("Close") }}
</button>
</div>
</div>
</div>
</div>
<BadgeGeneratorDialog ref="badgeGeneratorDialog" />
</template>
<script lang="ts">
import { Modal } from "bootstrap";
import BadgeGeneratorDialog from "./BadgeGeneratorDialog.vue";
export default {
components: {
BadgeGeneratorDialog
},
props: {},
emits: [],
data() {
return {
monitor: {
id: null,
name: null,
},
};
},
computed: {},
mounted() {
this.MonitorSettingDialog = new Modal(this.$refs.MonitorSettingDialog);
},
methods: {
/**
* Setting monitor
* @param {Object} group Data of monitor
* @param {Object} monitor Data of monitor
*/
show(group, monitor) {
this.monitor = {
id: monitor.element.id,
name: monitor.element.name,
monitor_index: monitor.index,
group_index: group.index,
isClickAble: this.showLink(monitor),
};
this.MonitorSettingDialog.show();
},
/**
* Toggle the value of sendUrl
* @param {number} groupIndex Index of group monitor is member of
* @param {number} index Index of monitor within group
*/
toggleLink(groupIndex, index) {
this.$root.publicGroupList[groupIndex].monitorList[index].sendUrl = !this.$root.publicGroupList[groupIndex].monitorList[index].sendUrl;
},
/**
* Should a link to the monitor be shown?
* Attempts to guess if a link should be shown based upon if
* sendUrl is set and if the URL is default or not.
* @param {Object} monitor Monitor to check
* @param {boolean} [ignoreSendUrl=false] Should the presence of the sendUrl
* property be ignored. This will only work in edit mode.
* @returns {boolean}
*/
showLink(monitor, ignoreSendUrl = false) {
// We must check if there are any elements in monitorList to
// prevent undefined errors if it hasn't been loaded yet
if (this.$parent.editMode && ignoreSendUrl && Object.keys(this.$root.monitorList).length) {
return this.$root.monitorList[monitor.element.id].type === "http" || this.$root.monitorList[monitor.element.id].type === "keyword";
}
return monitor.element.sendUrl && monitor.element.url && monitor.element.url !== "https://" && !this.editMode;
},
},
};
</script>
<style lang="scss" scoped>
@import "../assets/vars.scss";
.dark {
.modal-dialog .form-text, .modal-dialog p {
color: $dark-font-color;
}
}
</style>

View File

@@ -129,7 +129,9 @@ export default {
"ntfy": "Ntfy",
"octopush": "Octopush",
"OneBot": "OneBot",
"Opsgenie": "Opsgenie",
"PagerDuty": "PagerDuty",
"PagerTree": "PagerTree",
"pushbullet": "Pushbullet",
"PushByTechulus": "Push by Techulus",
"pushover": "Pushover",
@@ -143,6 +145,7 @@ export default {
"stackfield": "Stackfield",
"teams": "Microsoft Teams",
"telegram": "Telegram",
"twilio": "Twilio",
"Splunk": "Splunk",
"webhook": "Webhook",
"GoAlert": "GoAlert",

View File

@@ -11,16 +11,16 @@
</ul>
</div>
<div class="chart-wrapper" :class="{ loading : loading}">
<LineChart :chart-data="chartData" :options="chartOptions" />
<Line :data="chartData" :options="chartOptions" />
</div>
</div>
</template>
<script lang="js">
import { BarController, BarElement, Chart, Filler, LinearScale, LineController, LineElement, PointElement, TimeScale, Tooltip } from "chart.js";
import "chartjs-adapter-dayjs";
import "chartjs-adapter-dayjs-4";
import dayjs from "dayjs";
import { LineChart } from "vue-chart-3";
import { Line } from "vue-chartjs";
import { useToast } from "vue-toastification";
import { DOWN, PENDING, MAINTENANCE, log } from "../util.ts";
@@ -29,7 +29,7 @@ const toast = useToast();
Chart.register(LineController, BarController, LineElement, PointElement, TimeScale, BarElement, LinearScale, Tooltip, Filler);
export default {
components: { LineChart },
components: { Line },
props: {
/** ID of monitor */
monitorId: {
@@ -104,8 +104,10 @@ export default {
}
},
ticks: {
sampleSize: 3,
maxRotation: 0,
autoSkipPadding: 30,
padding: 3,
},
grid: {
color: this.$root.theme === "light" ? "rgba(0,0,0,0.1)" : "rgba(255,255,255,0.1)",
@@ -197,6 +199,7 @@ export default {
borderColor: "#5CDD8B",
backgroundColor: "#5CDD8B38",
yAxisID: "y",
label: "ping",
},
{
// Bar Chart
@@ -208,6 +211,8 @@ export default {
barThickness: "flex",
barPercentage: 1,
categoryPercentage: 1,
inflateAmount: 0.05,
label: "status",
},
],
};

View File

@@ -49,16 +49,15 @@
{{ monitor.element.name }}
</a>
<p v-else class="item-name"> {{ monitor.element.name }} </p>
<span
v-if="showLink(monitor, true)"
title="Toggle Clickable Link"
title="Setting"
>
<font-awesome-icon
v-if="editMode"
:class="{'link-active': monitor.element.sendUrl, 'btn-link': true}"
icon="link" class="action me-3"
@click="toggleLink(group.index, monitor.index)"
:class="{'link-active': true, 'btn-link': true}"
icon="cog" class="action me-3"
@click="$refs.monitorSettingDialog.show(group, monitor)"
/>
</span>
</div>
@@ -77,9 +76,11 @@
</div>
</template>
</Draggable>
<MonitorSettingDialog ref="monitorSettingDialog" />
</template>
<script>
import MonitorSettingDialog from "./MonitorSettingDialog.vue";
import Draggable from "vuedraggable";
import HeartbeatBar from "./HeartbeatBar.vue";
import Uptime from "./Uptime.vue";
@@ -87,6 +88,7 @@ import Tag from "./Tag.vue";
export default {
components: {
MonitorSettingDialog,
Draggable,
HeartbeatBar,
Uptime,
@@ -135,15 +137,6 @@ export default {
this.$root.publicGroupList[groupIndex].monitorList.splice(index, 1);
},
/**
* Toggle the value of sendUrl
* @param {number} groupIndex Index of group monitor is member of
* @param {number} index Index of monitor within group
*/
toggleLink(groupIndex, index) {
this.$root.publicGroupList[groupIndex].monitorList[index].sendUrl = !this.$root.publicGroupList[groupIndex].monitorList[index].sendUrl;
},
/**
* Should a link to the monitor be shown?
* Attempts to guess if a link should be shown based upon if

View File

@@ -6,7 +6,7 @@
'm-2': size == 'normal',
'px-2': size == 'sm',
'py-0': size == 'sm',
'm-1': size == 'sm',
'mx-1': size == 'sm',
}"
:style="{ backgroundColor: item.color, fontSize: size == 'sm' ? '0.7em' : '1em' }"
>

View File

@@ -76,11 +76,24 @@
</button>
</router-link>
</div>
<div v-if="allMonitorList.length > 0" class="pt-3 px-3">
<div v-if="allMonitorList.length > 0" class="pt-3">
<label class="form-label">{{ $t("Add a monitor") }}:</label>
<select v-model="selectedAddMonitor" class="form-control">
<option v-for="monitor in allMonitorList" :key="monitor.id" :value="monitor">{{ monitor.name }}</option>
</select>
<VueMultiselect
v-model="selectedAddMonitor"
:options="allMonitorList"
:multiple="false"
:searchable="true"
:placeholder="$t('Add a monitor')"
label="name"
trackBy="name"
class="mt-1"
>
<template #option="{ option }">
<div class="d-inline-flex">
<span>{{ option.name }} <Tag v-for="monitorTag in option.tags" :key="monitorTag" :item="monitorTag" :size="'sm'" /></span>
</div>
</template>
</VueMultiselect>
</div>
</div>
</div>
@@ -107,6 +120,7 @@
<script>
import { Modal } from "bootstrap";
import Confirm from "./Confirm.vue";
import Tag from "./Tag.vue";
import VueMultiselect from "vue-multiselect";
import { colorOptions } from "../util-frontend";
import { useToast } from "vue-toastification";
@@ -117,6 +131,7 @@ export default {
components: {
VueMultiselect,
Confirm,
Tag,
},
props: {
updated: {

View File

@@ -16,17 +16,29 @@
<input id="ntfy-priority" v-model="$parent.notification.ntfyPriority" type="number" class="form-control" required min="1" max="5" step="1">
</div>
<div class="mb-3">
<label for="ntfy-username" class="form-label">{{ $t("Username") }} ({{ $t("Optional") }})</label>
<label for="authentication-method" class="form-label">{{ $t("ntfyAuthenticationMethod") }}</label>
<select id="authentication-method" v-model="$parent.notification.ntfyAuthenticationMethod" class="form-select">
<option v-for="(name, type) in authenticationMethods" :key="type" :value="type">{{ name }}</option>
</select>
</div>
<div v-if="$parent.notification.ntfyAuthenticationMethod === 'usernamePassword'" class="mb-3">
<label for="ntfy-username" class="form-label">{{ $t("Username") }}</label>
<div class="input-group mb-3">
<input id="ntfy-username" v-model="$parent.notification.ntfyusername" type="text" class="form-control">
</div>
</div>
<div class="mb-3">
<label for="ntfy-password" class="form-label">{{ $t("Password") }} ({{ $t("Optional") }})</label>
<div v-if="$parent.notification.ntfyAuthenticationMethod === 'usernamePassword'" class="mb-3">
<label for="ntfy-password" class="form-label">{{ $t("Password") }}</label>
<div class="input-group mb-3">
<HiddenInput id="ntfy-password" v-model="$parent.notification.ntfypassword" autocomplete="new-password"></HiddenInput>
</div>
</div>
<div v-if="$parent.notification.ntfyAuthenticationMethod === 'accessToken'" class="mb-3">
<label for="ntfy-access-token" class="form-label">{{ $t("Access Token") }}</label>
<div class="input-group mb-3">
<HiddenInput id="ntfy-access-token" v-model="$parent.notification.ntfyaccesstoken"></HiddenInput>
</div>
</div>
<div class="mb-3">
<label for="ntfy-icon" class="form-label">{{ $t("IconUrl") }}</label>
<input id="ntfy-icon" v-model="$parent.notification.ntfyIcon" type="text" class="form-control">
@@ -40,11 +52,29 @@ export default {
components: {
HiddenInput,
},
computed: {
authenticationMethods() {
return {
none: this.$t("None"),
usernamePassword: this.$t("ntfyUsernameAndPassword"),
accessToken: this.$t("Access Token")
};
}
},
mounted() {
if (typeof this.$parent.notification.ntfyPriority === "undefined") {
this.$parent.notification.ntfyserverurl = "https://ntfy.sh";
this.$parent.notification.ntfyPriority = 5;
}
// Handling notifications that added before 1.22.0
if (typeof this.$parent.notification.ntfyAuthenticationMethod === "undefined") {
if (!this.$parent.notification.ntfyusername) {
this.$parent.notification.ntfyAuthenticationMethod = "none";
} else {
this.$parent.notification.ntfyAuthenticationMethod = "usernamePassword";
}
}
},
};
</script>

View File

@@ -0,0 +1,36 @@
<template>
<div class="mb-3">
<label for="opsgenie-region" class="form-label">{{ $t("Region") }}<span style="color: red;"><sup>*</sup></span></label>
<select id="opsgenie-region" v-model="$parent.notification.opsgenieRegion" class="form-select" required>
<option value="us">
US (Default)
</option>
<option value="eu">
EU
</option>
</select>
</div>
<div class="mb-3">
<label for="opsgenie-apikey" class="form-label">{{ $t("API Key") }}<span style="color: red;"><sup>*</sup></span></label>
<HiddenInput id="opsgenie-apikey" v-model="$parent.notification.opsgenieApiKey" required="true" autocomplete="false"></HiddenInput>
</div>
<div class="mb-3">
<label for="opsgenie-priority" class="form-label">{{ $t("Priority") }}</label>
<input id="opsgenie-priority" v-model="$parent.notification.opsgeniePriority" type="number" class="form-control" min="1" max="5" step="1">
</div>
<div class="form-text">
<span style="color: red;"><sup>*</sup></span>{{ $t("Required") }}
<i18n-t tag="p" keypath="aboutWebhooks" style="margin-top: 8px;">
<a href="https://docs.opsgenie.com/docs/alert-api" target="_blank">https://docs.opsgenie.com/docs/alert-api</a>
</i18n-t>
</div>
</template>
<script>
import HiddenInput from "../HiddenInput.vue";
export default {
components: {
HiddenInput,
},
};
</script>

View File

@@ -42,6 +42,8 @@
<option value="vibrate">{{ $t("pushoversounds vibrate") }}</option>
<option value="none">{{ $t("pushoversounds none") }}</option>
</select>
<label for="pushover-ttl" class="form-label">{{ $t("pushoverMessageTtl") }}</label>
<input id="pushover-ttl" v-model="$parent.notification.pushoverttl" type="number" min="0" step="1" class="form-control">
<div class="form-text">
<span style="color: red;"><sup>*</sup></span>{{ $t("Required") }}
<i18n-t tag="p" keypath="More info on:" style="margin-top: 8px;">

View File

@@ -97,7 +97,6 @@
(leave blank for default one)<br />
{{NAME}}: Service Name<br />
{{HOSTNAME_OR_URL}}: Hostname or URL<br />
{{URL}}: URL<br />
{{STATUS}}: Status<br />
</div>
</div>

View File

@@ -0,0 +1,27 @@
<template>
<div class="mb-3">
<label for="twilio-account-sid" class="form-label">{{ $t("Account SID") }}</label>
<input id="twilio-account-sid" v-model="$parent.notification.twilioAccountSID" type="text" class="form-control" required>
</div>
<div class="mb-3">
<label for="twilio-auth-token" class="form-label">{{ $t("Auth Token") }}</label>
<input id="twilio-auth-token" v-model="$parent.notification.twilioAuthToken" type="text" class="form-control" required>
</div>
<div class="mb-3">
<label for="twilio-from-number" class="form-label">{{ $t("From Number") }}</label>
<input id="twilio-from-number" v-model="$parent.notification.twilioFromNumber" type="text" class="form-control" required>
</div>
<div class="mb-3">
<label for="twilio-to-number" class="form-label">{{ $t("To Number") }}</label>
<input id="twilio-to-number" v-model="$parent.notification.twilioToNumber" type="text" class="form-control" required>
</div>
<div class="mb-3">
<i18n-t tag="p" keypath="More info on:" style="margin-top: 8px;">
<a href="https://www.twilio.com/docs/sms" target="_blank">https://www.twilio.com/docs/sms</a>
</i18n-t>
</div>
</template>

View File

@@ -21,6 +21,7 @@ import Mattermost from "./Mattermost.vue";
import Ntfy from "./Ntfy.vue";
import Octopush from "./Octopush.vue";
import OneBot from "./OneBot.vue";
import Opsgenie from "./Opsgenie.vue";
import PagerDuty from "./PagerDuty.vue";
import PagerTree from "./PagerTree.vue";
import PromoSMS from "./PromoSMS.vue";
@@ -41,6 +42,7 @@ import STMP from "./SMTP.vue";
import Teams from "./Teams.vue";
import TechulusPush from "./TechulusPush.vue";
import Telegram from "./Telegram.vue";
import Twilio from "./Twilio.vue";
import Webhook from "./Webhook.vue";
import WeCom from "./WeCom.vue";
import GoAlert from "./GoAlert.vue";
@@ -76,6 +78,7 @@ const NotificationFormList = {
"ntfy": Ntfy,
"octopush": Octopush,
"OneBot": OneBot,
"Opsgenie": Opsgenie,
"PagerDuty": PagerDuty,
"PagerTree": PagerTree,
"promosms": PromoSMS,
@@ -95,6 +98,7 @@ const NotificationFormList = {
"stackfield": Stackfield,
"teams": Teams,
"telegram": Telegram,
"twilio": Twilio,
"Splunk": Splunk,
"webhook": Webhook,
"WeCom": WeCom,

View File

@@ -1,21 +1,18 @@
<template>
<div class="my-4">
<div class="mx-4 pt-1 my-3">
<div class="mx-0 mx-lg-4 pt-1 mb-4">
<button class="btn btn-primary" @click.stop="addTag"><font-awesome-icon icon="plus" /> {{ $t("Add New Tag") }}</button>
</div>
<div class="tags-list my-3">
<div v-for="(tag, index) in tagsList" :key="tag.id" class="d-flex align-items-center mx-4 py-1 tags-list-row" :disabled="processing" @click="editTag(index)">
<div class="col-5 ps-1">
<div v-for="(tag, index) in tagsList" :key="tag.id" class="d-flex align-items-center mx-0 mx-lg-4 py-1 tags-list-row" :disabled="processing" @click="editTag(index)">
<div class="col-10 col-sm-5">
<Tag :item="tag" />
</div>
<div class="col-5 px-1">
<div class="col-5 px-1 d-none d-sm-block">
<div>{{ monitorsByTag(tag.id).length }} {{ $tc("Monitor", monitorsByTag(tag.id).length) }}</div>
</div>
<div class="col-2 pe-3 d-flex justify-content-end">
<button type="button" class="btn ms-2 py-1">
<font-awesome-icon class="" icon="edit" />
</button>
<div class="col-2 pe-2 pe-lg-3 d-flex justify-content-end">
<button type="button" class="btn-rm-tag btn btn-outline-danger ms-2 py-1" :disabled="processing" @click.stop="deleteConfirm(index)">
<font-awesome-icon class="" icon="trash" />
</button>
@@ -156,8 +153,8 @@ export default {
@import "../../assets/vars.scss";
.btn-rm-tag {
padding-left: 11px;
padding-right: 11px;
padding-left: 9px;
padding-right: 9px;
}
.tags-list .tags-list-row {

View File

@@ -41,7 +41,8 @@ const languageList = {
"el-GR": "Ελληνικά",
"yue": "繁體中文 (廣東話 / 粵語)",
"ro": "Limba română",
"ur": "Urdu"
"ur": "Urdu",
"ge": "ქართული"
};
let messages = {

View File

@@ -49,6 +49,7 @@ import {
faFilter,
faInfoCircle,
faClone,
faCertificate,
} from "@fortawesome/free-solid-svg-icons";
library.add(
@@ -95,6 +96,7 @@ library.add(
faFilter,
faInfoCircle,
faClone,
faCertificate,
);
export { FontAwesomeIcon };

View File

@@ -683,6 +683,6 @@
"backupDescription2": "ملحوظة",
"languageName": "العربية",
"Game": "الألعاب",
"List": "قائمة",
"List": "القائمة",
"statusMaintenance": "الصيانة"
}

View File

@@ -178,7 +178,7 @@
"Degraded Service": "Всички услуги са недостъпни",
"Add Group": "Добави група",
"Add a monitor": "Добави монитор",
"Edit Status Page": "Редактиране Статус страница",
"Edit Status Page": "Редактиране на статус страницата",
"Go to Dashboard": "Към Таблото",
"telegram": "Telegram",
"webhook": "Уеб кука",
@@ -200,7 +200,7 @@
"mattermost": "Mattermost",
"Status Page": "Статус страница",
"Status Pages": "Статус страници",
"Primary Base URL": "Основен базов URL адрес",
"Primary Base URL": "Базов URL адрес",
"Push URL": "Генериран Push URL адрес",
"needPushEvery": "Необходимо е да извършвате заявка към този URL адрес на всеки {0} секунди.",
"pushOptionalParams": "Допълнителни, но не задължителни параметри: {0}",
@@ -591,7 +591,7 @@
"All Status Pages": "Всички статус страници",
"Select status pages...": "Изберете статус страници…",
"recurringIntervalMessage": "Изпълнявай ежедневно | Изпълнявай всеки {0} дни",
"affectedMonitorsDescription": "Изберете монитори, засегнати от текущата поддръжка",
"affectedMonitorsDescription": "Изберете монитори, попадащи в обсега на текущата поддръжка",
"affectedStatusPages": "Покажи това съобщение за поддръжка на избрани статус страници",
"atLeastOneMonitor": "Изберете поне един засегнат монитор",
"deleteMaintenanceMsg": "Сигурни ли сте, че желаете да изтриете тази поддръжка?",
@@ -652,7 +652,7 @@
"dnsCacheDescription": "Възможно е да не работи в IPv6 среда - деактивирайте, ако срещнете проблеми.",
"Single Maintenance Window": "Единичен времеви интервал за поддръжка",
"Maintenance Time Window of a Day": "Времеви интервал от деня за поддръжка",
"Effective Date Range": "Интервал от дни на влизане в сила",
"Effective Date Range": "Ефективен интервал от дни (по желание)",
"Schedule Maintenance": "Планирай поддръжка",
"Date and Time": "Дата и час",
"DateTime Range": "Изтрий времеви интервал",
@@ -681,7 +681,7 @@
"infiniteRetention": "Задайте стойност 0 за безкрайно съхранение.",
"Monitor": "Монитор | Монитори",
"dataRetentionTimeError": "Периодът на съхранение трябва да е 0 или по-голям",
"confirmDeleteTagMsg": "Сигурни ли сте, че желаете да изтриете този таг? Мониторите, свързани с него, няма да бъдат изтрити.",
"confirmDeleteTagMsg": "Сигурни ли сте, че желаете да изтриете този етикет? Мониторите, свързани с него, няма да бъдат изтрити.",
"promosmsAllowLongSMS": "Позволи дълъг SMS",
"Packet Size": "Размер на пакет",
"Custom Monitor Type": "Потребителски тип монитор",
@@ -694,7 +694,7 @@
"confirmUninstallPlugin": "Сигурни ли сте, че желаете да деинсталирате този плъгин?",
"markdownSupported": "Поддържа се Markdown синтаксис",
"Google Analytics ID": "Google Analytics ID",
"Edit Tag": "Редактиране на таг",
"Edit Tag": "Редактиране на етикет",
"Learn More": "Научете повече",
"Server Address": "Сървър адрес",
"notificationRegional": "Регионални",
@@ -707,7 +707,7 @@
"telegramSendSilently": "Изпрати тихо",
"Clone Monitor": "Клониране на монитор",
"Clone": "Клонирай",
"cloneOf": "Клонинг на {0}",
"cloneOf": "Клониран {0}",
"Expiry": "Валиден до",
"Expiry date": "Дата на изтичане",
"Add Another": "Добави друг",
@@ -734,5 +734,55 @@
"wayToGetPagerTreeIntegrationURL": "След като създадете интеграция на Uptime Kuma в PagerTree, копирайте крайната точка. За пълни подробности вижте {0}",
"pagertreeIntegrationUrl": "URL Адрес за интеграция",
"pagertreeMedium": "Средна",
"pagertreeCritical": "Критична"
"pagertreeCritical": "Критична",
"Add New Tag": "Добави нов етикет",
"lunaseaTarget": "Цел",
"lunaseaDeviceID": "ID на устройството",
"lunaseaUserID": "ID на потребител",
"twilioAccountSID": "Профил SID",
"twilioAuthToken": "Удостоверяващ токен",
"twilioFromNumber": "От номер",
"twilioToNumber": "Към номер",
"sameAsServerTimezone": "Kато часовата зона на сървъра",
"startDateTime": "Старт Дата/Час",
"endDateTime": "Край Дата/Час",
"cronSchedule": "График: ",
"invalidCronExpression": "Невалиден \"Cron\" израз: {0}",
"cronExpression": "Израз тип \"Cron\"",
"statusPageRefreshIn": "Обновяване след: {0}",
"ntfyUsernameAndPassword": "Потребителско име и парола",
"ntfyAuthenticationMethod": "Метод за удостоверяване",
"pushoverMessageTtl": "TTL на съобщението (секунди)",
"Open Badge Generator": "Отвори генератора на баджове",
"Badge Generator": "Генератор на баджове на {0}",
"Badge Type": "Тип бадж",
"Badge Duration": "Продължителност на баджа",
"Badge Prefix": "Префикс на баджа",
"Badge Label Color": "Цвят на етикета на баджа",
"Badge Color": "Цвят на баджа",
"Badge Label Suffix": "Суфикс на етикета на значката",
"Badge Up Color": "Цвят на баджа за достъпен",
"Badge Down Color": "Цвят на баджа за недостъпен",
"Badge Maintenance Color": "Цвят на баджа за поддръжка",
"Badge Warn Color": "Цвят на баджа за предупреждение",
"Badge Warn Days": "Дни за показване на баджа",
"Badge Style": "Стил на баджа",
"Badge value (For Testing only.)": "Стойност на баджа (само за тест.)",
"Badge URL": "URL адрес на баджа",
"Monitor Setting": "Настройка на монитор {0}",
"Show Clickable Link": "Покажи връзка, която може да се кликне",
"Show Clickable Link Description": "Ако е отбелязано, всеки който има достъп до тази статус страница, ще може да достъпва URL адреса на монитора.",
"Badge Label": "Етикет на баджа",
"Badge Suffix": "Суфикс на баджа",
"Badge Label Prefix": "Префикс на етикета на значката",
"Badge Pending Color": "Цвят на баджа за изчакващ",
"Badge Down Days": "Колко дни баджът да не се показва",
"Group": "Група",
"Monitor Group": "Монитор група",
"Cannot connect to the socket server": "Не може да се свърже със сокет сървъра",
"Reconnecting...": "Повторно свързване...",
"Edit Maintenance": "Редактиране на поддръжка",
"Home": "Главна страница",
"noGroupMonitorMsg": "Не е налично. Първо създайте групов монитор.",
"Close": "Затвори"
}

28
src/lang/ca.json Normal file
View File

@@ -0,0 +1,28 @@
{
"Settings": "Paràmetres",
"Dashboard": "Tauler",
"Help": "Ajuda",
"New Update": "Nova actualització",
"Language": "Idioma",
"Appearance": "Aparença",
"Theme": "Tema",
"General": "General",
"Game": "Joc",
"Version": "Versió",
"Check Update On GitHub": "Comprovar actualitzacions a GitHub",
"List": "Llista",
"Home": "Inici",
"Add": "Afegir",
"Add New Monitor": "Afegir nou monitor",
"Quick Stats": "Estadístiques ràpides",
"Up": "Funcional",
"Down": "Caigut",
"Pending": "Pendent",
"Maintenance": "Manteniment",
"Unknown": "Desconegut",
"Cannot connect to the socket server": "No es pot connectar al servidor socket",
"Reconnecting...": "S'està tornant a connectar...",
"languageName": "Català",
"Primary Base URL": "URL Base Primària",
"statusMaintenance": "Manteniment"
}

46
src/lang/ckb.json Normal file
View File

@@ -0,0 +1,46 @@
{
"languageName": "کوردی",
"Settings": "ڕێکخستنەکان",
"Help": "یارمەتی",
"New Update": "وەشانی نوێ",
"Language": "زمان",
"Appearance": "ڕووکار",
"Theme": "شێوەی ڕووکار",
"General": "گشتی",
"Game": "یاری",
"Version": "وەشان",
"Check Update On GitHub": "سەیری وەشانی نوێ بکە لە Github",
"List": "لیست",
"Add": "زیادکردن",
"Quick Stats": "ئاماری خێرا",
"Up": "سەروو",
"Down": "خواروو",
"Pending": "هەڵپەسێردراو",
"statusMaintenance": "چاکردنەوە",
"Maintenance": "چاکردنەوە",
"Unknown": "نەزانراو",
"Passive Monitor Type": "جۆری مۆنیتەری پاسیڤ",
"Specific Monitor Type": "جۆری مۆنیتەری تایبەت",
"markdownSupported": "ڕستەسازی مارکداون پشتگیری دەکرێت",
"pauseDashboardHome": "وچان",
"Pause": "وچان",
"Name": "ناو",
"Status": "دۆخ",
"Message": "پەیام",
"No important events": "هیچ ڕووداوێکی گرنگ نییە",
"Resume": "‬دەستپێکردنەوە",
"Edit": "بژارکردن",
"Delete": "سڕینەوە",
"Uptime": "کاتی کارکردن",
"Cert Exp.": "بەسەرچوونی بڕوانامەی SSL.",
"day": "ڕۆژ | ڕۆژەکان",
"-day": "-ڕۆژ",
"hour": "کاتژمێر",
"Dashboard": "داشبۆرد",
"Primary Base URL": "بەستەری بنچینەیی سەرەکی",
"Add New Monitor": "مۆنیتەرێکی نوێ زیاد بکە",
"General Monitor Type": "جۆری مۆنیتەری گشتی",
"DateTime": "رێکەوت",
"Current": "هەنووکە",
"Monitor": "مۆنیتەر | مۆنیتەرەکان"
}

View File

@@ -1,5 +1,5 @@
{
"languageName": "Czech",
"languageName": "Čeština",
"checkEverySecond": "Kontrolovat každých {0} sekund",
"retryCheckEverySecond": "Opakovat každých {0} sekund",
"resendEveryXTimes": "Znovu zaslat {0}krát",
@@ -134,7 +134,7 @@
"Remember me": "Zapamatovat si mě",
"Login": "Přihlášení",
"No Monitors, please": "Žádné dohledy, prosím",
"add one": "přidat jeden",
"add one": "začněte přidáním nového",
"Notification Type": "Typ oznámení",
"Email": "E-mail",
"Test": "Test",
@@ -518,7 +518,7 @@
"PushDeer Key": "PushDeer klíč",
"Footer Text": "Text v patičce",
"Show Powered By": "Zobrazit \"Poskytuje\"",
"Domain Names": "Názvy domén",
"Domain Names": "Doménová jména",
"signedInDisp": "Přihlášen jako {0}",
"signedInDispDisabled": "Ověření je vypnuté.",
"RadiusSecret": "Tajemství Radius",
@@ -542,11 +542,11 @@
"promosmsPassword": "API Password",
"pushoversounds pushover": "Pushover (výchozí)",
"pushoversounds bike": "Kolo",
"pushoversounds bugle": "Bugle",
"pushoversounds bugle": "Trumpeta",
"pushoversounds cashregister": "Pokladna",
"pushoversounds classical": "Classical",
"pushoversounds cosmic": "Kosmický",
"pushoversounds falling": "Falling",
"pushoversounds falling": "Padající",
"pushoversounds gamelan": "Gamelan",
"pushoversounds incoming": "Příchozí",
"pushoversounds intermission": "Přestávka",
@@ -554,9 +554,9 @@
"pushoversounds mechanical": "Mechanika",
"pushoversounds pianobar": "Barové piano",
"pushoversounds siren": "Siréna",
"pushoversounds spacealarm": "Space Alarm",
"pushoversounds tugboat": "Tug Boat",
"pushoversounds alien": "Alien Alarm (dlouhý)",
"pushoversounds spacealarm": "Vesmírný alarm",
"pushoversounds tugboat": "Remorkér",
"pushoversounds alien": "Mimozemský poplach (dlouhý)",
"pushoversounds climb": "Climb (dlouhý)",
"pushoversounds persistent": "Persistent (dlouhý)",
"pushoversounds echo": "Pushover Echo (dlouhý)",
@@ -661,7 +661,7 @@
"dnsCacheDescription": "V některých IPv6 prostředích nemusí fungovat. Pokud narazíte na nějaké problémy, vypněte jej.",
"Single Maintenance Window": "Konkrétní časové okno pro údržbu",
"Maintenance Time Window of a Day": "Časové okno pro údržbu v daný den",
"Effective Date Range": "Časové období",
"Effective Date Range": "Časové období (volitelné)",
"Schedule Maintenance": "Naplánovat údržbu",
"Date and Time": "Datum a čas",
"DateTime Range": "Rozsah data a času",
@@ -669,7 +669,7 @@
"Free Mobile User Identifier": "Identifikátor uživatele Free Mobile",
"Free Mobile API Key": "API klíč Free Mobile",
"Enable TLS": "Povolit TLS",
"Proto Service Name": "Proto Service Name",
"Proto Service Name": "Jméno Proto Service",
"Proto Method": "Proto metoda",
"Proto Content": "Proto obsah",
"Economy": "Úsporná",
@@ -705,9 +705,9 @@
"telegramProtectContent": "Ochrana přeposílání/ukládání",
"telegramSendSilently": "Odeslat potichu",
"telegramSendSilentlyDescription": "Zprávu odešle tiše. Uživatelé obdrží oznámení bez zvuku.",
"Clone": "Klonovat",
"cloneOf": "Klonovat {0}",
"Clone Monitor": "Klonovat dohled",
"Clone": "Duplikovat",
"cloneOf": "Kopie {0}",
"Clone Monitor": "Duplikovat dohled",
"API Keys": "API klíče",
"Expiry": "Platnost",
"Don't expire": "Nevyprší",
@@ -736,7 +736,53 @@
"pagertreeHigh": "Nahlas",
"wayToGetPagerTreeIntegrationURL": "Po vytvoření integrace Uptime Kuma v aplikaci PagerTree zkopírujte koncový bod. Zobrazit všechny podrobnosti {0}",
"Add New Tag": "Přidat nový štítek",
"lunaseaTarget": "cíl",
"lunaseaTarget": "Cíl",
"lunaseaDeviceID": "ID zařízení",
"lunaseaUserID": "ID uživatele"
"lunaseaUserID": "ID uživatele",
"statusPageRefreshIn": "Obnovení za: {0}",
"twilioAccountSID": "SID účtu",
"twilioFromNumber": "Číslo odesílatele",
"twilioToNumber": "Číslo příjemce",
"twilioAuthToken": "Autorizační token",
"sameAsServerTimezone": "Stejné jako časové pásmo serveru",
"cronExpression": "Cron výraz",
"cronSchedule": "Plán: ",
"invalidCronExpression": "Neplatný cron výraz: {0}",
"startDateTime": "Počáteční datum/čas",
"endDateTime": "Datum/čas konce",
"ntfyAuthenticationMethod": "Způsob ověření",
"ntfyUsernameAndPassword": "Uživatelské jméno a heslo",
"pushoverMessageTtl": "Zpráva TTL (Sekund)",
"Show Clickable Link": "Zobrazit klikatelný odkaz",
"Show Clickable Link Description": "Pokud je zaškrtnuto, všichni, kdo mají přístup k této stavové stránce, mají přístup k adrese URL monitoru.",
"Open Badge Generator": "Otevřít generátor odznaků",
"Badge Type": "Typ odznaku",
"Badge Duration": "Platnost odznaku",
"Badge Label": "Štítek odznaku",
"Badge Prefix": "Prefix odznaku",
"Monitor Setting": "{0}'s Nastavení dohledu",
"Badge Generator": "Generátor odznaků pro {0}",
"Badge Label Color": "Barva štítku odznaku",
"Badge Color": "Barva odznaku",
"Badge Style": "Styl odznaku",
"Badge Label Suffix": "Přípona štítku odznaku",
"Badge URL": "URL odznaku",
"Badge Suffix": "Přípona odznaku",
"Badge Label Prefix": "Prefix štítku odznaku",
"Badge Up Color": "Barva odznaku při Běží",
"Badge Down Color": "Barva odznaku při Nedostupné",
"Badge Pending Color": "Barva odznaku při Pauze",
"Badge Maintenance Color": "Barva odznaku při Údržbě",
"Badge Warn Color": "Barva odznaku při Upozornění",
"Reconnecting...": "Obnovení spojení...",
"Cannot connect to the socket server": "Nelze se připojit k soketovému serveru",
"Edit Maintenance": "Upravit Údržbu",
"Home": "Hlavní stránka",
"Badge Down Days": "Odznak nedostupných dní",
"Group": "Skupina",
"Monitor Group": "Sledovaná skupina",
"noGroupMonitorMsg": "Není k dispozici. Nejprve vytvořte skupin dohledů.",
"Close": "Zavřít",
"Badge value (For Testing only.)": "Hodnota odznaku (pouze pro testování)",
"Badge Warn Days": "Odznak dní s upozorněním"
}

View File

@@ -37,7 +37,7 @@
"checkEverySecond": "Tjek hvert {0} sekund",
"Response": "Respons",
"Ping": "Ping",
"Monitor Type": "Overvåger Type",
"Monitor Type": "Overvåger type",
"Keyword": "Nøgleord",
"Friendly Name": "Visningsnavn",
"URL": "URL",
@@ -144,7 +144,7 @@
"retryCheckEverySecond": "Prøv igen hvert {0} sekund.",
"importHandleDescription": "Vælg 'Spring over eksisterende', hvis du vil springe over hver overvåger eller underretning med samme navn. 'Overskriv' sletter alle eksisterende overvågere og underretninger.",
"confirmImportMsg": "Er du sikker på at importere sikkerhedskopien? Sørg for, at du har valgt den rigtige importindstilling.",
"Heartbeat Retry Interval": "Hjerteslag Gentagelsesinterval",
"Heartbeat Retry Interval": "Hjerteslag gentagelsesinterval",
"Import Backup": "Importer Backup",
"Export Backup": "Eksporter Backup",
"Skip existing": "Spring over eksisterende",
@@ -166,14 +166,14 @@
"Purple": "Lilla",
"Pink": "Pink",
"Search...": "Søg…",
"Avg. Ping": "Gns. Ping",
"Avg. Response": "Gns. Respons",
"Avg. Ping": "Gns. ping",
"Avg. Response": "Gns. respons",
"Entry Page": "Entry Side",
"statusPageNothing": "Intet her, tilføj venligst en Gruppe eller en Overvåger.",
"No Services": "Ingen Tjenester",
"All Systems Operational": "Alle Systemer i Drift",
"Partially Degraded Service": "Delvist Forringet Service",
"Degraded Service": "Forringet Service",
"Partially Degraded Service": "Delvist forringet service",
"Degraded Service": "Forringet service",
"Add Group": "Tilføj Gruppe",
"Add a monitor": "Tilføj en Overvåger",
"Edit Status Page": "Rediger Statusside",
@@ -314,7 +314,7 @@
"Steam API Key": "Steam API-nøgle",
"Shrink Database": "Krymp Database",
"Pick a RR-Type...": "Vælg en RR-Type…",
"Pick Accepted Status Codes...": "Vælg Accepterede Statuskoder...",
"Pick Accepted Status Codes...": "Vælg accepterede statuskoder",
"Default": "Standard",
"HTTP Options": "HTTP Valgmuligheder",
"Create Incident": "Opret Annoncering",
@@ -447,7 +447,7 @@
"Docker Hosts": "Docker Hosts",
"loadingError": "Kan ikke hente dataene, prøv igen senere.",
"Custom": "Brugerdefineret",
"Monitor": "Monitor | Monitors",
"Monitor": "Overvåger | Overvågere",
"Specific Monitor Type": "Specifik monitor-type",
"topic": "Emne",
"Fingerprint:": "Fingerprint:",
@@ -580,5 +580,7 @@
"Expiry date": "Udløbsdato",
"Expires": "Udløber",
"deleteAPIKeyMsg": "Er du sikker på du vil slette denne API nøgle?",
"pagertreeDoNothing": "Gør intet"
"pagertreeDoNothing": "Gør intet",
"Start of maintenance": "Start på vedligeholdelse",
"Add New Tag": "Tilføj nyt tag"
}

View File

@@ -10,6 +10,7 @@
"Version": "Version",
"Check Update On GitHub": "Auf GitHub nach Updates suchen",
"List": "Liste",
"Home": "Home",
"Add": "Hinzufügen",
"Add New Monitor": "Neuen Monitor hinzufügen",
"Quick Stats": "Übersicht",
@@ -17,6 +18,8 @@
"Down": "Inaktiv",
"Pending": "Ausstehend",
"Unknown": "Unbekannt",
"Cannot connect to the socket server": "Es kann keine Verbindung zum Socket-Server hergestellt werden",
"Reconnecting...": "Die Verbindung wird wiederhergestellt...",
"Pause": "Pausieren",
"pauseDashboardHome": "Pausiert",
"Name": "Name",
@@ -102,7 +105,7 @@
"deleteNotificationMsg": "Möchtest du diese Benachrichtigung wirklich für alle Monitore löschen?",
"resolverserverDescription": "Cloudflare ist als der Standardserver festgelegt. Dieser kann jederzeit geändert werden.",
"Resolver Server": "Auflösungsserver",
"rrtypeDescription": "Wähle den RR-Typ aus, welchen du überwachen möchtest.",
"rrtypeDescription": "Wähle den RR Typ aus, welchen du überwachen möchtest",
"Last Result": "Letztes Ergebnis",
"pauseMonitorMsg": "Bist du sicher, dass du den Monitor pausieren möchtest?",
"clearEventsMsg": "Bist du sicher, dass du alle Ereignisse für diesen Monitor löschen möchtest?",
@@ -206,7 +209,7 @@
"mattermost": "Mattermost",
"Primary Base URL": "Primär URL",
"Push URL": "Push URL",
"needPushEvery": "Du solltest diese URL alle {0} Sekunden aufrufen",
"needPushEvery": "Du solltest diese URL alle {0} Sekunden aufrufen.",
"pushOptionalParams": "Optionale Parameter: {0}",
"defaultNotificationName": "Mein {notification} Alarm ({number})",
"here": "hier",
@@ -259,6 +262,7 @@
"More info on:": "Mehr Infos auf: {0}",
"pushoverDesc1": "Notfallpriorität (2) hat standardmässig 30 Sekunden Auszeit zwischen den Versuchen und läuft nach 1 Stunde ab.",
"pushoverDesc2": "Fülle das Geräte Feld aus, wenn du Benachrichtigungen an verschiedene Geräte senden möchtest.",
"pushoverMessageTtl": "Message TTL (Sekunden)",
"SMS Type": "SMS Typ",
"octopushTypePremium": "Premium (Schnell - zur Benachrichtigung empfohlen)",
"octopushTypeLowCost": "Kostengünstig (Langsam - manchmal vom Betreiber gesperrt)",
@@ -276,10 +280,10 @@
"appriseInstalled": "Apprise ist installiert.",
"appriseNotInstalled": "Apprise ist nicht installiert. {0}",
"Access Token": "Access Token",
"Channel access token": "Channel access token",
"Channel access token": "Channel Access Token",
"Line Developers Console": "Line Developers Console",
"lineDevConsoleTo": "Line Developers Console - {0}",
"Basic Settings": "Basic Settings",
"Basic Settings": "Grundeinstellungen",
"User ID": "User ID",
"Messaging API": "Messaging API",
"wayToGetLineChannelToken": "Rufe zuerst {0} auf, erstelle dann einen Provider und Channel (Messaging API). Als nächstes kannst du den Channel access token und die User ID aus den oben genannten Menüpunkten abrufen.",
@@ -298,7 +302,7 @@
"Internal Room Id": "Interne Raum-ID",
"matrixDesc1": "Die interne Raum-ID findest du im erweiterten Bereich der Raumeinstellungen im Matrix-Client. Es sollte aussehen wie z.B. !QMdRCpUIfLwsfjxye6:home.server.",
"matrixDesc2": "Es wird dringend empfohlen einen neuen Benutzer anzulegen und nicht den Zugriffstoken deines eigenen Matrix-Benutzers zu verwenden. Anderenfalls ermöglicht es vollen Zugriff auf dein Konto und alle Räume, denen du beigetreten bist. Erstelle stattdessen einen neuen Benutzer und lade ihn nur in den Raum ein, in dem du die Benachrichtigung erhalten möchtest. Du kannst den Zugriffstoken erhalten, indem du Folgendes ausführst {0}",
"Method": "Method",
"Method": "Methode",
"Body": "Body",
"Headers": "Headers",
"PushUrl": "Push URL",
@@ -348,7 +352,7 @@
"Services": "Dienste",
"Discard": "Verwerfen",
"Cancel": "Abbrechen",
"Powered by": "Powered by",
"Powered by": "Erstellt mit",
"shrinkDatabaseDescription": "Löse VACUUM für die SQLite Datenbank aus. Wenn die Datenbank nach 1.10.0 erstellt wurde, ist AUTO_VACUUM bereits aktiviert und diese Aktion ist nicht erforderlich.",
"serwersms": "SerwerSMS.pl",
"serwersmsAPIUser": "API Benutzername (inkl. webapi_ prefix)",
@@ -533,7 +537,7 @@
"Also check beta release": "Auch nach beta Versionen schauen",
"Using a Reverse Proxy?": "Wird ein Reverse Proxy genutzt?",
"Check how to config it for WebSocket": "Prüfen, wie er für die Nutzung mit WebSocket konfiguriert wird",
"Steam Game Server": "Steam Game Server",
"Steam Game Server": "Steam Spielserver",
"Most likely causes:": "Wahrscheinliche Ursachen:",
"The resource is no longer available.": "Die Quelle ist nicht mehr verfügbar.",
"There might be a typing error in the address.": "Es gibt einen Tippfehler in der Adresse.",
@@ -590,7 +594,7 @@
"atLeastOneMonitor": "Wähle mindestens einen Monitor",
"deleteMaintenanceMsg": "Möchtest du diese Wartung löschen?",
"Base URL": "Basis URL",
"goAlertInfo": "GoAlert ist eine Open-Source Applikation für Rufbereitschaftsplanung, automatische Eskalation und Benachrichtigung (z.B. SMS oder Telefonanrufe). Beauftragen Sie automatisch die richtige Person, auf die richtige Art und Weise und zum richtigen Zeitpunkt. {0}",
"goAlertInfo": "GoAlert ist eine Open-Source Applikation für Rufbereitschaftsplanung, automatische Eskalation und Benachrichtigung (z.B. SMS oder Telefonanrufe). Engagiere automatisch die richtige Person, auf die richtige Art und Weise und zum richtigen Zeitpunkt! {0}",
"goAlertIntegrationKeyInfo": "Bekommt einen generischen API Schlüssel in folgenden Format \"aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee\". Normalerweise entspricht dies dem Wert des Token aus der URL.",
"goAlert": "GoAlert",
"backupOutdatedWarning": "Veraltet: Da viele Funktionen hinzugefügt wurden und die Backupfunktion nicht mehr gepflegt wird, kann keine vollständige Sicherung erstellt oder wiederhergestellt werden.",
@@ -599,13 +603,13 @@
"squadcast": "Squadcast",
"SendKey": "SendKey",
"SMSManager API Docs": "SMSManager API Dokumente ",
"Gateway Type": "Gateway Type",
"Gateway Type": "Gateway Typ",
"SMSManager": "SMSManager",
"You can divide numbers with": "Du kannst Zahlen teilen mit",
"or": "oder",
"recurringInterval": "Intervall",
"Recurring": "Wiederkehrend",
"strategyManual": "Active/Inactive Manually",
"strategyManual": "Aktiv/Inaktiv Manuell",
"warningTimezone": "Es wird die Zeitzone des Servers genutzt",
"weekdayShortMon": "Mo",
"weekdayShortTue": "Di",
@@ -640,12 +644,12 @@
"installing": "Installiere",
"uninstall": "Deinstallieren",
"uninstalling": "Deinstalliere",
"confirmUninstallPlugin": "Möchten Sie dieses Plugin wirklich deinstallieren?",
"confirmUninstallPlugin": "Möchtest du dieses Plugin wirklich deinstallieren?",
"notificationRegional": "Regional",
"Single Maintenance Window": "Einmaliges Wartungsfenster",
"dnsCacheDescription": "In einigen IPv6-Umgebungen funktioniert es möglicherweise nicht. Deaktivieren Sie es, wenn Sie auf Probleme stossen.",
"dnsCacheDescription": "In einigen IPv6-Umgebungen funktioniert es möglicherweise nicht. Deaktiviere es, wenn Probleme auftreten.",
"Maintenance Time Window of a Day": "Wartungszeitfenster eines Tages",
"Effective Date Range": "Gültigkeitsbereich",
"Effective Date Range": "Gültigkeitsbereich (Optional)",
"Schedule Maintenance": "Wartung planen",
"Date and Time": "Datum und Uhrzeit",
"DateTime Range": "Datums- und Zeitbereich",
@@ -674,15 +678,15 @@
"Don't expire": "Nicht ablaufen",
"Add Another": "Hinzufügen",
"Key Added": "Schlüssel hinzugefügt",
"apiKeyAddedMsg": "Ihr API Schlüssel wurde hinzugefügt. Bitte notieren Sie Ihn, da er nicht erneut angezeigt wird.",
"apiKeyAddedMsg": "API Schlüssel wurde hinzugefügt. Bitte notiere den Schlüssel, da er nicht erneut angezeigt wird.",
"Add API Key": "API Schlüssel hinzufügen",
"No API Keys": "Kein API Schlüssel",
"apiKey-active": "Aktiv",
"apiKey-expired": "Abgelaufen",
"apiKey-inactive": "Inaktiv",
"Expires": "Läuft ab",
"disableAPIKeyMsg": "Sind Sie sicher, dass Sie diesen API Schlüssel deaktivieren möchten?",
"deleteAPIKeyMsg": "Sind Sie sicher, dass Sie diesen API Schlüssel löschen möchten?",
"disableAPIKeyMsg": "Bist du sicher, dass du diesen API Schlüssel deaktivieren willst?",
"deleteAPIKeyMsg": "Bist du sicher, dass du diesen API Schlüssel löschen willst?",
"Generate": "Generieren",
"infiniteRetention": "Für unendliche Speicherung auf 0 setzen.",
"dataRetentionTimeError": "Aufbewahrungsfrist muss grösser oder gleich 0 sein",
@@ -691,8 +695,8 @@
"cloneOf": "Klon von {0}",
"wayToGetZohoCliqURL": "Wie eine Webhook URL erstellt werden kann, erfährst du {0}.",
"enableGRPCTls": "Senden von gRPC Anforderungen mit TLS Verbindung zulassen",
"grpcMethodDescription": "Der Name der Methode wird in das \"cammelCase \"-Format konvertiert (z.B. sayHello, check, etc.)",
"wayToGetKookGuildID": "Schalten Sie den „Entwicklermodus“ in den Kook-Einstellungen ein und klicken Sie mit der rechten Maustaste auf die Gilde, um ihre ID zu erhalten",
"grpcMethodDescription": "Der Name der Methode wird in das \"cammelCase\" Format konvertiert (z.B. sayHello, check, etc.)",
"wayToGetKookGuildID": "Schalte den „Entwicklermodus“ in den Kook-Einstellungen ein und klicke mit der rechten Maustaste auf die Gilde, um die ID zu erhalten",
"Guild ID": "Gilde ID",
"Lowcost": "Kostengünstig",
"high": "hoch",
@@ -712,7 +716,7 @@
"pagertreeCritical": "Kritisch",
"pagertreeResolve": "Automatisch auflösen",
"pagertreeDoNothing": "Nichts tun",
"wayToGetPagerTreeIntegrationURL": "Nachdem Sie die Uptime Kuma Integration in PagerTree erstellt haben, kopieren Sie den Endpunkt. Siehe vollständige Details {0}",
"wayToGetPagerTreeIntegrationURL": "Nachdem du die Uptime Kuma Integration in PagerTree erstellt hast, kopiere den Endpunkt. Siehe details {0}",
"Server Address": "Serveradresse",
"Learn More": "Erfahre mehr",
"Edit Tag": "Tag editieren",
@@ -724,12 +728,58 @@
"smseagleEncoding": "Als Unicode senden",
"smseaglePriority": "Nachrichtenpriorität (0-9, Standard = 0)",
"smseagleContact": "Telefonbuch Kontaktname(n)",
"confirmDeleteTagMsg": "Sind Sie sicher, dass Sie diesen Tag löschen möchten? Monitore, die mit diesem Tag verknüpft sind, werden nicht gelöscht.",
"wayToGetKookBotToken": "Erstellen Sie eine Anwendung und erhalten Sie Ihren Bot-Token unter {0}",
"confirmDeleteTagMsg": "Möchtest du dieses Tag wirklich löschen? Monitore, die mit diesem Tag verknüpft sind, werden nicht gelöscht.",
"wayToGetKookBotToken": "Erstelle eine Anwendung und erhalte den Bot-Token unter {0}",
"Strategy": "Strategie",
"Free Mobile User Identifier": "Kostenlose mobile Benutzerkennung",
"smseagleGroup": "Telefonbuch Gruppenname(n)",
"smseagleRecipient": "Empfänger (mehrere müssen durch Komma getrennt werden)",
"API Keys": "API Schlüssel",
"Continue": "Weiter"
"Continue": "Weiter",
"Add New Tag": "Neuen Tag hinzufügen",
"lunaseaTarget": "Ziel",
"lunaseaDeviceID": "Geräte-ID",
"lunaseaUserID": "Benutzer-ID",
"ntfyAuthenticationMethod": "Authentifizierungsmethode",
"ntfyUsernameAndPassword": "Benutzername und Passwort",
"twilioAccountSID": "Account SID",
"twilioFromNumber": "Absender",
"twilioToNumber": "Empfänger",
"twilioAuthToken": "Auth Token",
"statusPageRefreshIn": "Aktualisierung in: {0}",
"sameAsServerTimezone": "Gleiche Zeitzone wie Server",
"startDateTime": "Start Datum/Uhrzeit",
"endDateTime": "Ende Datum/Uhrzeit",
"cronExpression": "Cron-Ausdruck",
"cronSchedule": "Zeitplan: ",
"invalidCronExpression": "Ungültiger Cron-Ausdruck: {0}",
"Open Badge Generator": "Open Badge Generator",
"Badge Generator": "{0}'s Badge Generator",
"Badge Type": "Badge Typ",
"Badge Duration": "Badge Dauer",
"Badge Label": "Badge Label",
"Badge Prefix": "Badge Präfix",
"Badge Suffix": "Badge Suffix",
"Badge Label Color": "Badge Label Farbe",
"Badge Color": "Badge Farbe",
"Badge Label Prefix": "Badge Label Präfix",
"Badge Up Color": "Badge Up Farbe",
"Badge Maintenance Color": "Badge Wartung Farbe",
"Badge Warn Color": "Badge Warnung Farbe",
"Badge Warn Days": "Badge Warnung Tage",
"Badge Style": "Badge Stil",
"Badge URL": "Badge URL",
"Badge Pending Color": "Badge Pending Farbe",
"Badge Down Days": "Badge Down Tage",
"Monitor Setting": "{0}'s Monitor Einstellung",
"Show Clickable Link": "Klickbaren Link anzeigen",
"Badge Label Suffix": "Badge Label Suffix",
"Badge value (For Testing only.)": "Badge Wert (nur für Tests)",
"Show Clickable Link Description": "Wenn diese Option aktiviert ist, kann jeder, der Zugriff auf diese Statusseite hat, auf die Monitor URL zugreifen.",
"Badge Down Color": "Badge Down Farbe",
"Edit Maintenance": "Wartung bearbeiten",
"Group": "Gruppe",
"Monitor Group": "Monitor Gruppe",
"noGroupMonitorMsg": "Nicht verfügbar. Erstelle zunächst einen Gruppenmonitor.",
"Close": "Schliessen"
}

View File

@@ -10,6 +10,7 @@
"Version": "Version",
"Check Update On GitHub": "Auf GitHub nach Updates suchen",
"List": "Liste",
"Home": "Home",
"Add": "Hinzufügen",
"Add New Monitor": "Neuen Monitor hinzufügen",
"Quick Stats": "Übersicht",
@@ -17,6 +18,8 @@
"Down": "Inaktiv",
"Pending": "Ausstehend",
"Unknown": "Unbekannt",
"Cannot connect to the socket server": "Es kann keine Verbindung zum Socket-Server hergestellt werden",
"Reconnecting...": "Die Verbindung wird wiederhergestellt...",
"Pause": "Pausieren",
"pauseDashboardHome": "Pausiert",
"Name": "Name",
@@ -259,6 +262,7 @@
"More info on:": "Mehr Infos auf: {0}",
"pushoverDesc1": "Notfallpriorität (2) hat standardmäßig 30 Sekunden Auszeit zwischen den Versuchen und läuft nach 1 Stunde ab.",
"pushoverDesc2": "Fülle das Geräte Feld aus, wenn du Benachrichtigungen an verschiedene Geräte senden möchtest.",
"pushoverMessageTtl": "Message TTL (Sekunden)",
"SMS Type": "SMS Typ",
"octopushTypePremium": "Premium (Schnell - zur Benachrichtigung empfohlen)",
"octopushTypeLowCost": "Kostengünstig (Langsam - manchmal vom Betreiber gesperrt)",
@@ -590,7 +594,7 @@
"atLeastOneMonitor": "Wähle mindestens einen Monitor",
"deleteMaintenanceMsg": "Möchtest du diese Wartung löschen?",
"Base URL": "Basis URL",
"goAlertInfo": "GoAlert ist eine Open-Source Applikation für Rufbereitschaftsplanung, automatische Eskalation und Benachrichtigung (z.B. SMS oder Telefonanrufe). Beauftragen Sie automatisch die richtige Person, auf die richtige Art und Weise und zum richtigen Zeitpunkt. {0}",
"goAlertInfo": "GoAlert ist eine Open-Source Applikation für Rufbereitschaftsplanung, automatische Eskalation und Benachrichtigung (z.B. SMS oder Telefonanrufe). Engagiere automatisch die richtige Person, auf die richtige Art und Weise und zum richtigen Zeitpunkt! {0}",
"goAlertIntegrationKeyInfo": "Bekommt einen generischen API Schlüssel in folgenden Format \"aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee\". Normalerweise entspricht dies dem Wert des Token aus der URL.",
"goAlert": "GoAlert",
"backupOutdatedWarning": "Veraltet: Da viele Funktionen hinzugefügt wurden und diese Sicherungsfunktion nicht mehr gepflegt wird, kann keine vollständige Sicherung erstellen oder wiederherstellen werden.",
@@ -605,9 +609,9 @@
"or": "oder",
"recurringInterval": "Intervall",
"Recurring": "Wiederkehrend",
"Single Maintenance Window": "Einzigartiges Wartungsfenster",
"Single Maintenance Window": "Einmaliges Wartungsfenster",
"Maintenance Time Window of a Day": "Zeitfenster für die Wartung",
"Effective Date Range": "Bereich der Wirksamkeitsdaten",
"Effective Date Range": "Bereich der Wirksamkeitsdaten (Optional)",
"strategyManual": "Aktiv/Inaktiv Manuell",
"warningTimezone": "Es wird die Zeitzone des Servers verwendet",
"weekdayShortMon": "Mo",
@@ -626,6 +630,7 @@
"lastDay4": "4. letzter Tag im Monat",
"No Maintenance": "Keine Wartung",
"Schedule Maintenance": "Wartung planen",
"Edit Maintenance": "Wartung bearbeiten",
"pauseMaintenanceMsg": "Möchtest du wirklich pausieren?",
"maintenanceStatus-under-maintenance": "Unter Wartung",
"maintenanceStatus-inactive": "Inaktiv",
@@ -646,19 +651,19 @@
"Disable": "Deaktivieren",
"Custom Monitor Type": "Benutzerdefinierter Monitortyp",
"webhookAdditionalHeadersDesc": "Legt zusätzliche Header fest, die mit der Webhook gesendet wurden.",
"dnsCacheDescription": "In einigen IPv6-Umgebungen funktioniert es möglicherweise nicht. Deaktivieren Sie es, wenn Sie auf Probleme stoßen.",
"dnsCacheDescription": "In einigen IPv6-Umgebungen funktioniert es möglicherweise nicht. Deaktiviere es, wenn Probleme auftreten.",
"loadingError": "Die Daten konnten nicht abgerufen werden, bitte später noch einmal versuchen.",
"confirmUninstallPlugin": "Möchten Sie dieses Plugin wirklich deinstallieren?",
"grpcMethodDescription": "Der Name der Methode wird in das \"cammelCase \"-Format konvertiert (z.B. sayHello, check, etc.)",
"confirmUninstallPlugin": "Möchtest du dieses Plugin wirklich deinstallieren?",
"grpcMethodDescription": "Der Name der Methode wird in das \"cammelCase\"-Format konvertiert (z.B. sayHello, check, etc.)",
"Passive Monitor Type": "Passiver Monitortyp",
"Specific Monitor Type": "Spezifischer Monitortyp",
"webhookAdditionalHeadersTitle": "Zusätzliche Header",
"Packet Size": "Paketgröße",
"IconUrl": "Symbol-URL",
"wayToGetZohoCliqURL": "Erfahren Sie, wie Sie eine Webhook-URL {0} erstellen.",
"wayToGetZohoCliqURL": "Wie eine Webhook URL erstellt werden kann, erfährst du {0}.",
"dataRetentionTimeError": "Aufbewahrungszeit muss 0 oder größer sein",
"infiniteRetention": "Für unendliche Aufbewahrung auf 0 setzen.",
"confirmDeleteTagMsg": "Möchten Sie dieses Tag wirklich löschen? Mit diesem Tag verknüpfte Monitore werden nicht gelöscht.",
"confirmDeleteTagMsg": "Möchtest du dieses Tag wirklich löschen? Monitore, die mit diesem Tag verknüpft sind, werden nicht gelöscht.",
"enableGRPCTls": "Senden von gRPC-Anforderungen mit TLS-Verbindung zulassen",
"ZohoCliq": "ZohoCliq",
"Monitor": "Überwachung | Monitore",
@@ -668,8 +673,8 @@
"uninstall": "Deinstallieren",
"uninstalling": "Deinstallation",
"markdownSupported": "Markdown-Syntax unterstützt",
"wayToGetKookBotToken": "Erstellen Sie eine Anwendung und erhalten Sie Ihren Bot-Token unter {0}",
"wayToGetKookGuildID": "Schalten Sie den „Entwicklermodus“ in den Kook-Einstellungen ein und klicken Sie mit der rechten Maustaste auf die Gilde, um ihre ID zu erhalten",
"wayToGetKookBotToken": "Erstelle eine Anwendung und erhalte den Bot-Token unter {0}",
"wayToGetKookGuildID": "Schalte den „Entwicklermodus“ in den Kook-Einstellungen ein und klicke mit der rechten Maustaste auf die Gilde, um die ID zu erhalten",
"Guild ID": "Guild-ID",
"Free Mobile User Identifier": "Kostenlose mobile Benutzerkennung",
"Free Mobile API Key": "Kostenloser Mobile API-Schlüssel",
@@ -687,7 +692,7 @@
"smseagleGroup": "Telefonbuch Gruppenname(n)",
"smseagleContact": "Telefonbuch Kontaktname(n)",
"smseagleRecipientType": "Empfängertyp",
"smseagleRecipient": "Empfänger (mehrere müssen mit Komma getrennt werden)",
"smseagleRecipient": "Empfänger (mehrere müssen durch Komma getrennt werden)",
"smseagleToken": "API-Zugriffstoken",
"smseagleUrl": "Ihre SMSEagle-Geräte-URL",
"Kook": "Kook",
@@ -730,9 +735,54 @@
"telegramProtectContentDescription": "Die Bot-Nachrichten in Telegram sind gegen Weiterleitung und Speichern geschützt.",
"notificationRegional": "Regional",
"Key Added": "Schlüssel hinzugefügt",
"apiKeyAddedMsg": "Ihr API Schlüssel wurde hinzugefügt. Bitte notieren Sie Ihn, da er nicht erneut angezeigt wird.",
"apiKeyAddedMsg": "API Schlüssel wurde hinzugefügt. Bitte notiere den Schlüssel, da er nicht erneut angezeigt wird.",
"telegramMessageThreadID": "(Optional) Nachrichten Thread ID",
"telegramMessageThreadIDDescription": "Optionale eindeutige Kennung für den Ziel-Thread (Thema) des Forums; nur für Forum-Supergroups",
"telegramSendSilently": "Stumm Senden",
"telegramSendSilentlyDescription": "Sende die Nachricht stumm. Nutzer bekommen eine Benachrichtigung ohne Ton."
"telegramSendSilentlyDescription": "Sende die Nachricht stumm. Nutzer bekommen eine Benachrichtigung ohne Ton.",
"Add New Tag": "Neuen Tag hinzufügen",
"lunaseaDeviceID": "Geräte-ID",
"lunaseaTarget": "Ziel",
"lunaseaUserID": "Benutzer-ID",
"ntfyAuthenticationMethod": "Authentifizierungsmethode",
"ntfyUsernameAndPassword": "Benutzername und Passwort",
"twilioAccountSID": "Account SID",
"twilioFromNumber": "Absender",
"twilioToNumber": "Empfänger",
"twilioAuthToken": "Auth Token",
"statusPageRefreshIn": "Aktualisierung in: {0}",
"sameAsServerTimezone": "Gleiche Zeitzone wie Server",
"startDateTime": "Start Datum/Uhrzeit",
"endDateTime": "Ende Datum/Uhrzeit",
"cronExpression": "Cron-Ausdruck",
"cronSchedule": "Zeitplan: ",
"invalidCronExpression": "Ungültiger Cron-Ausdruck: {0}",
"Show Clickable Link": "Klickbaren Link anzeigen",
"Open Badge Generator": "Open Badge Generator",
"Badge Generator": "{0}'s Badge Generator",
"Badge Type": "Badge Typ",
"Badge Duration": "Badge Dauer",
"Badge Label": "Badge Label",
"Show Clickable Link Description": "Wenn diese Option aktiviert ist, kann jeder, der Zugriff auf diese Statusseite hat, auf die Monitor-URL zugreifen.",
"Badge Label Color": "Badge Label Farbe",
"Badge Color": "Badge Farbe",
"Badge Label Prefix": "Badge Label Präfix",
"Badge Label Suffix": "Badge Label Suffix",
"Badge Maintenance Color": "Badge Wartung Farbe",
"Badge Warn Color": "Badge Warnung Farbe",
"Badge Style": "Badge Stil",
"Badge value (For Testing only.)": "Badge Wert (nur für Tests)",
"Badge URL": "Badge URL",
"Badge Up Color": "Badge Up Farbe",
"Badge Down Color": "Badge Down Farbe",
"Badge Pending Color": "Badge Pending Farbe",
"Badge Down Days": "Badge Down Tage",
"Monitor Setting": "{0}'s Monitor Einstellung",
"Badge Prefix": "Badge Präfix",
"Badge Suffix": "Badge Suffix",
"Badge Warn Days": "Badge Warnung Tage",
"Group": "Gruppe",
"Monitor Group": "Monitor Gruppe",
"noGroupMonitorMsg": "Nicht verfügbar. Erstelle zunächst einen Gruppenmonitor.",
"Close": "Schließen"
}

View File

@@ -695,5 +695,7 @@
"Learn More": "Μάθετε περισσότερα",
"Free Mobile User Identifier": "Free Mobile User Identifier",
"Free Mobile API Key": "Free Mobile API Key",
"smseaglePriority": "Προτεραιότητα μηνύματος (0-9, προεπιλογή = 0)"
"smseaglePriority": "Προτεραιότητα μηνύματος (0-9, προεπιλογή = 0)",
"statusPageRefreshIn": "Ανανέωση σε {0}",
"Add New Tag": "Πρόσθεσε νέα ετικέτα"
}

View File

@@ -13,6 +13,7 @@
"Version": "Version",
"Check Update On GitHub": "Check Update On GitHub",
"List": "List",
"Home": "Home",
"Add": "Add",
"Add New Monitor": "Add New Monitor",
"Quick Stats": "Quick Stats",
@@ -22,6 +23,8 @@
"statusMaintenance": "Maintenance",
"Maintenance": "Maintenance",
"Unknown": "Unknown",
"Cannot connect to the socket server": "Cannot connect to the socket server",
"Reconnecting...": "Reconnecting...",
"General Monitor Type": "General Monitor Type",
"Passive Monitor Type": "Passive Monitor Type",
"Specific Monitor Type": "Specific Monitor Type",
@@ -174,6 +177,7 @@
"Avg. Response": "Avg. Response",
"Entry Page": "Entry Page",
"statusPageNothing": "Nothing here, please add a group or a monitor.",
"statusPageRefreshIn": "Refresh in: {0}",
"No Services": "No Services",
"All Systems Operational": "All Systems Operational",
"Partially Degraded Service": "Partially Degraded Service",
@@ -393,6 +397,12 @@
"backupRecommend": "Please backup the volume or the data folder (./data/) directly instead.",
"Optional": "Optional",
"or": "or",
"sameAsServerTimezone": "Same as Server Timezone",
"startDateTime": "Start Date/Time",
"endDateTime": "End Date/Time",
"cronExpression": "Cron Expression",
"cronSchedule": "Schedule: ",
"invalidCronExpression": "Invalid Cron Expression: {0}",
"recurringInterval": "Interval",
"Recurring": "Recurring",
"strategyManual": "Active/Inactive Manually",
@@ -428,8 +438,9 @@
"dnsCacheDescription": "It may be not working in some IPv6 environments, disable it if you encounter any issues.",
"Single Maintenance Window": "Single Maintenance Window",
"Maintenance Time Window of a Day": "Maintenance Time Window of a Day",
"Effective Date Range": "Effective Date Range",
"Effective Date Range": "Effective Date Range (Optional)",
"Schedule Maintenance": "Schedule Maintenance",
"Edit Maintenance": "Edit Maintenance",
"Date and Time": "Date and Time",
"DateTime Range": "DateTime Range",
"loadingError": "Cannot fetch the data, please try again later.",
@@ -549,6 +560,7 @@
"More info on:": "More info on: {0}",
"pushoverDesc1": "Emergency priority (2) has default 30 second timeout between retries and will expire after 1 hour.",
"pushoverDesc2": "If you want to send notifications to different devices, fill out Device field.",
"pushoverMessageTtl": "Message TTL (Seconds)",
"SMS Type": "SMS Type",
"octopushTypePremium": "Premium (Fast - recommended for alerting)",
"octopushTypeLowCost": "Low Cost (Slow - sometimes blocked by operator)",
@@ -706,5 +718,39 @@
"wayToGetPagerTreeIntegrationURL": "After creating the Uptime Kuma integration in PagerTree, copy the Endpoint. See full details {0}",
"lunaseaTarget": "Target",
"lunaseaDeviceID": "Device ID",
"lunaseaUserID": "User ID"
"lunaseaUserID": "User ID",
"ntfyAuthenticationMethod": "Authentication Method",
"ntfyUsernameAndPassword": "Username and Password",
"twilioAccountSID": "Account SID",
"twilioAuthToken": "Auth Token",
"twilioFromNumber": "From Number",
"twilioToNumber": "To Number",
"Monitor Setting": "{0}'s Monitor Setting",
"Show Clickable Link": "Show Clickable Link",
"Show Clickable Link Description": "If checked everyone who have access to this status page can have access to monitor URL.",
"Open Badge Generator": "Open Badge Generator",
"Badge Generator": "{0}'s Badge Generator",
"Badge Type": "Badge Type",
"Badge Duration": "Badge Duration",
"Badge Label": "Badge Label",
"Badge Prefix": "Badge Prefix",
"Badge Suffix": "Badge Suffix",
"Badge Label Color": "Badge Label Color",
"Badge Color": "Badge Color",
"Badge Label Prefix": "Badge Label Prefix",
"Badge Label Suffix": "Badge Label Suffix",
"Badge Up Color": "Badge Up Color",
"Badge Down Color": "Badge Down Color",
"Badge Pending Color": "Badge Pending Color",
"Badge Maintenance Color": "Badge Maintenance Color",
"Badge Warn Color": "Badge Warn Color",
"Badge Warn Days": "Badge Warn Days",
"Badge Down Days": "Badge Down Days",
"Badge Style": "Badge Style",
"Badge value (For Testing only.)": "Badge value (For Testing only.)",
"Badge URL": "Badge URL",
"Group": "Group",
"Monitor Group": "Monitor Group",
"noGroupMonitorMsg": "Not Available. Create a Group Monitor First.",
"Close": "Close"
}

View File

@@ -303,7 +303,7 @@
"Maintenance": "Mantenimiento",
"General Monitor Type": "Monitor Tipo General",
"Specific Monitor Type": "Monitor Tipo Específico",
"Monitor": "Monitores",
"Monitor": "Monitor | Monitores",
"Resend Notification if Down X times consecutively": "Reenviar Notificación si Caído X veces consecutivamente",
"resendEveryXTimes": "Reenviar cada {0} veces",
"resendDisabled": "Reenvío deshabilitado",
@@ -679,15 +679,15 @@
"serwersms": "SerwerSMS.pl",
"serwersmsAPIUser": "Nombre de usuario de API (inc. webapi_ prefix)",
"smseagleGroup": "Nombre(s) de grupo de Guía Telefónica",
"Unpin": "Quitar de destacados",
"Unpin": "Dejar de Fijar",
"Prefix Custom Message": "Prefijo personalizado",
"markdownSupported": "Soporta sintaxis Markdown",
"markdownSupported": "Sintaxis de Markdown soportada",
"Server Address": "Dirección del Servidor",
"Learn More": "Aprende Más",
"Pick a RR-Type...": "Seleccione un Tipo RR",
"onebotHttpAddress": "Dirección HTTP OneBot",
"SendKey": "Clave de Envío",
"octopushAPIKey": "\"Clave API\" de las credenciales HTTP API en el panel de control",
"octopushAPIKey": "\"Clave API\" desde credenciales API HTTP en panel de control",
"octopushLogin": "\"Inicio de Sesión\" a partir de las credenciales API HTTP en el panel de control",
"ntfy Topic": "Tema ntfy",
"Google Analytics ID": "ID Analíticas de Google",
@@ -696,5 +696,62 @@
"Bark Endpoint": "Endpoint Bark",
"WebHookUrl": "WebHookUrl",
"High": "Alto",
"alertaApiEndpoint": "Endpoint API"
"alertaApiEndpoint": "Endpoint API",
"Body Encoding": "Codificación del cuerpo",
"Expiry date": "Fecha de expiración",
"Expiry": "Expiración",
"API Keys": "Claves API",
"Key Added": "Clave añadida",
"Add Another": "Añadir otro",
"Continue": "Continuar",
"Don't expire": "No caduca",
"apiKey-inactive": "Inactivo",
"apiKey-expired": "Expirado",
"apiKey-active": "Activo",
"No API Keys": "No hay claves API",
"Add API Key": "Añadir clave API",
"apiKeyAddedMsg": "Su clave API ha sido añadida. Anótala, ya que no se volverá a mostrar.",
"Clone": "Clonar",
"cloneOf": "Clon de {0}",
"pagertreeDoNothing": "No hacer nada",
"pagertreeResolve": "Resolución automática",
"pagertreeCritical": "Crítico",
"pagertreeHigh": "Alto",
"pagertreeMedium": "Medio",
"pagertreeLow": "Bajo",
"pagertreeSilent": "Silencio",
"pagertreeUrgency": "Urgencia",
"pagertreeIntegrationUrl": "URL de integración",
"lunaseaTarget": "Objetivo",
"wayToGetPagerTreeIntegrationURL": "Después de crear la integración Uptime Kuma en PagerTree, copie el Endpoint. Ver todos los detalles {0}",
"Generate": "Generar",
"deleteAPIKeyMsg": "¿Está seguro de que desea eliminar esta clave API?",
"telegramMessageThreadID": "(Opcional) ID del hilo de mensajes",
"telegramMessageThreadIDDescription": "Opcional Identificador único para el hilo de mensajes de destino (asunto) del foro; solo para supergrupos de foros",
"telegramProtectContent": "Proteger Forwarding/Saving",
"telegramProtectContentDescription": "Si se activa, los mensajes del bot en Telegram estarán protegidos contra el reenvío y el guardado.",
"notificationRegional": "Regional",
"Clone Monitor": "Clonar Monitor",
"telegramSendSilently": "Enviar en silencio",
"telegramSendSilentlyDescription": "Envía el mensaje en silencio. Los usuarios recibirán una notificación sin sonido.",
"Add New Tag": "Añadir nueva etiqueta",
"lunaseaUserID": "ID Usuario",
"lunaseaDeviceID": "ID Dispositivo",
"disableAPIKeyMsg": "¿Está seguro de que desea desactivar esta clave API?",
"Expires": "Expira",
"twilioAccountSID": "SID de Cuenta",
"twilioFromNumber": "Desde el numero",
"twilioToNumber": "Hasta el numero",
"startDateTime": "Fecha/Hora Inicio",
"sameAsServerTimezone": "Igual a Zona horaria del Servidor",
"endDateTime": "Fecha/Hora Fin",
"cronExpression": "Expresión Cron",
"cronSchedule": "Cronograma: ",
"invalidCronExpression": "Expresión Cron invalida:{0}",
"statusPageRefreshIn": "Reinicio en: {0}",
"twilioAuthToken": "Token de Autentificación",
"ntfyUsernameAndPassword": "Nombre de Usuario y Contraseña",
"ntfyAuthenticationMethod": "Método de Autentificación",
"Cannot connect to the socket server": "No se puede conectar al servidor socket",
"Reconnecting...": "Reconectando..."
}

View File

@@ -74,7 +74,7 @@
"Heartbeat Retry Interval": "Pultsu errepikatze interbaloak",
"Advanced": "Aurreratua",
"Upside Down Mode": "Alderantzizkako modua",
"Max. Redirects": "Berbideratze max.",
"Max. Redirects": "Birbideratze max.",
"Accepted Status Codes": "Onartutako egoera kodeak",
"Push URL": "Push URLa",
"needPushEvery": "URL hau {0} segunduro deitu beharko zenuke.",
@@ -159,7 +159,7 @@
"Token": "Tokena",
"Show URI": "Erakutsi URIa",
"Tags": "Etiketak",
"Add New below or Select...": "Gehitu beste bat behean edo hautatu...",
"Add New below or Select...": "Gehitu beste bat behean edo hautatu",
"Tag with this name already exist.": "Izen hau duen etiketa dagoeneko badago.",
"Tag with this value already exist.": "Balio hau duen etiketa dagoeneko badago.",
"color": "kolorea",
@@ -172,7 +172,7 @@
"Indigo": "Indigo",
"Purple": "Morea",
"Pink": "Arrosa",
"Search...": "Bilatu...",
"Search...": "Bilatu",
"Avg. Ping": "Batazbesteko Pinga",
"Avg. Response": "Batazbesteko erantzuna",
"Entry Page": "Sarrera orria",
@@ -218,7 +218,7 @@
"wayToGetDiscordURL": "You can get this by going to Server Settings -> Integrations -> Create Webhook",
"Bot Display Name": "Bot Display Name",
"Prefix Custom Message": "Prefix Custom Message",
"Hello @everyone is...": "Hello {'@'}everyone is...",
"Hello @everyone is...": "Kaixo {'@'}edonor da…",
"teams": "Microsoft Teams",
"Webhook URL": "Webhook URL",
"wayToGetTeamsURL": "You can learn how to create a webhook URL {0}.",
@@ -325,7 +325,7 @@
"Steam API Key": "Steam API Giltza",
"Shrink Database": "Shrink Datubasea",
"Pick a RR-Type...": "Pick a RR-Type...",
"Pick Accepted Status Codes...": "Hautatu onartutako egoera kodeak...",
"Pick Accepted Status Codes...": "Hautatu onartutako egoera kodeak",
"Default": "Lehenetsia",
"HTTP Options": "HTTP Aukerak",
"Create Incident": "Sortu inzidentzia",
@@ -527,7 +527,7 @@
"There might be a typing error in the address.": "Idazketa-akats bat egon daiteke helbidean.",
"What you can try:": "Probatu dezakezuna:",
"Retype the address.": "Berridatzi helbidea.",
"Go back to the previous page.": "Itzuli aurreko orrialdera",
"Go back to the previous page.": "Itzuli aurreko orrialdera.",
"Coming Soon": "Laster",
"wayToGetClickSendSMSToken": "API erabiltzailea and API giltza hemendik lortu ditzakezu: {0} .",
"Connection String": "Konexio katea",
@@ -537,5 +537,39 @@
"ntfy Topic": "ntfy Topic",
"Domain": "Domeinua",
"Workstation": "Lan gunea",
"disableCloudflaredNoAuthMsg": "Ez Auth moduan zaude, pasahitza ez da beharrezkoa."
"disableCloudflaredNoAuthMsg": "Ez Auth moduan zaude, pasahitza ez da beharrezkoa.",
"maintenanceStatus-ended": "Bukatuta",
"maintenanceStatus-unknown": "Ezezaguna",
"Enable": "Gaitu",
"Strategy": "Estrategia",
"General Monitor Type": "Monitorizazio mota orokorra",
"Select status pages...": "Hautatu egoera orriak…",
"Server Address": "Zerbitzari helbidea",
"Learn More": "Ikasi gehiago",
"weekdayShortTue": "Ast",
"weekdayShortWed": "Asz",
"Disable": "Desgaitu",
"warningTimezone": "Zerbitzariaren orduzona erabiltzen ari da",
"weekdayShortThu": "Og",
"weekdayShortMon": "Asl",
"Base URL": "Oinarri URLa",
"high": "altua",
"Economy": "Ekonomia",
"Help": "Laguntza",
"Game": "Jokoa",
"statusMaintenance": "Mantenuan",
"Maintenance": "Mantenua",
"Passive Monitor Type": "Monitorizazio mota pasiboa",
"Specific Monitor Type": "Zehaztutako monitorizazio mota",
"markdownSupported": "Markdown sintaxia onartzen du",
"Monitor": "Monitorizazio | Monitorizazioak",
"resendDisabled": "Berbidaltzea desgaituta",
"weekdayShortFri": "Ost",
"weekdayShortSat": "Lar",
"weekdayShortSun": "Iga",
"dayOfWeek": "Asteko eguna",
"dayOfMonth": "Hilabeteko eguna",
"lastDay": "Azken eguna",
"lastDay1": "Hilabeteko azken eguna",
"Resend Notification if Down X times consecutively": "Bidali jakinarazpena X aldiz jarraian erortzen bada"
}

View File

@@ -1,34 +1,34 @@
{
"languageName": "Farsi",
"checkEverySecond": "بررسی هر {0} ثانیه.",
"retryCheckEverySecond": "تکرار مجدد هر {0} ثانیه.",
"retriesDescription": "حداکثر تعداد تکرار پیش از علامت گذاری وب‌سایت بعنوان خارج از دسترس و ارسال اطلاع‌رسانی.",
"languageName": "فارسی",
"checkEverySecond": "بررسی هر {0} ثانیه",
"retryCheckEverySecond": "تکرار مجدد هر {0} ثانیه",
"retriesDescription": "حداکثر تعداد تکرار پیش از علامت گذاری وب‌سایت بعنوان خارج از دسترس و ارسال اطلاع‌رسانی",
"ignoreTLSError": "بی‌خیال ارور TLS/SSL برای سایت‌های HTTPS",
"upsideDownModeDescription": "نتیجه وضعیت را برعکس کن، مثلا اگر سرویس در دسترس بود فرض کن که سرویس پایین است!",
"upsideDownModeDescription": "نتیجه وضعیت را برعکس کن، مثلا اگر سرویس در دسترس بود فرض کن که سرویس پایین است.",
"maxRedirectDescription": "حداکثر تعداد ریدایرکتی که سرویس پشتیبانی کند. برای اینکه ری‌دایرکت‌ها پشتیبانی نشوند، عدد 0 را وارد کنید.",
"acceptedStatusCodesDescription": "لطفا HTTP Status Code هایی که میخواهید به عنوان پاسخ موفقیت آمیز در نظر گرفته شود را انتخاب کنید.",
"passwordNotMatchMsg": "تکرار رمز عبور مطابقت ندارد!",
"passwordNotMatchMsg": "تکرار رمز عبور مطابقت ندارد.",
"notificationDescription": "برای اینکه سرویس اطلاع‌رسانی کار کند، آنرا به یکی از مانیتور‌ها متصل کنید.",
"keywordDescription": "در نتیجه درخواست (اهمیتی ندارد پاسخ JSON است یا HTML) بدنبال این کلمه بگرد (حساس به کوچک/بزرگ بودن حروف).",
"pauseDashboardHome": "متوقف شده",
"deleteMonitorMsg": "آیا از حذف این مانیتور مطمئن هستید؟",
"deleteNotificationMsg": "آیا مطمئن هستید که میخواهید این سرویس اطلاع‌رسانی را برای تمامی مانیتورها حذف کنید؟",
"resolverserverDescription": "سرویس CloudFlare به عنوان سرور پیش‌فرض استفاده می‌شود، شما میتوانید آنرا به هر سرور دیگری بعدا تغییر دهید.",
"rrtypeDescription": "لطفا نوع Resource Record را انتخاب کنید.",
"rrtypeDescription": "لطفا نوع Resource Record را انتخاب کنید",
"pauseMonitorMsg": "آیا مطمئن هستید که میخواهید این مانیتور را متوقف کنید ؟",
"enableDefaultNotificationDescription": "برای هر مانیتور جدید، این سرویس اطلاع‌رسانی به صورت پیش‌فرض فعال خواهد شد. البته که شما میتوانید به صورت دستی آنرا برای هر مانیتور به صورت جداگانه غیر فعال کنید.",
"clearEventsMsg": "آیا از اینکه تمامی تاریخچه رویداد‌های این مانیتور حذف شود مطمئن هستید؟",
"clearHeartbeatsMsg": "آیا از اینکه تاریخچه تمامی Heartbeat های این مانیتور حذف شود مطمئن هستید؟ ",
"clearHeartbeatsMsg": "آیا از اینکه تاریخچه تمامی ضربان قلب های این مانیتور حذف شود مطمئن هستید؟",
"confirmClearStatisticsMsg": "آیا از حذف تمامی آمار و ارقام مطمئن هستید؟",
"importHandleDescription": " اگر که میخواهید بیخیال مانیتورها و یا سرویس‌های اطلاع‌رسانی که با نام مشابه از قبل موجود هستند شوید، گزینه 'بی‌خیال موارد ..' را انتخاب کنید. توجه کنید که گزینه 'بازنویسی' تمامی موارد موجود با نام مشابه را از بین خواهد برد.",
"confirmImportMsg": "آیا از بازگردانی بک آپ مطمئن هستید؟ لطفا از اینکه نوع بازگردانی درستی را انتخاب کرده‌اید اطمینان حاصل کنید!",
"twoFAVerifyLabel": "لطفا جهت اطمینان از عملکرد احراز هویت دو مرحله‌ای توکن خود را وارد کنید!",
"tokenValidSettingsMsg": "توکن شما معتبر است، هم اکنون میتوانید احراز هویت دو مرحله‌ای را فعال کنید!",
"confirmEnableTwoFAMsg": " آیا از فعال سازی احراز هویت دو مرحله‌ای مطمئن هستید؟",
"importHandleDescription": "اگر که میخواهید بیخیال مانیتورها و یا سرویس‌های اطلاع‌رسانی که با نام مشابه از قبل موجود هستند شوید، گزینه 'بی‌خیال موارد ..' را انتخاب کنید. توجه کنید که گزینه 'بازنویسی' تمامی موارد موجود با نام مشابه را از بین خواهد برد.",
"confirmImportMsg": "آیا از بازگردانی بک آپ مطمئن هستید؟ لطفا از اینکه نوع بازگردانی درستی را انتخاب کرده‌اید اطمینان حاصل کنید.",
"twoFAVerifyLabel": "لطفا جهت اطمینان از عملکرد احراز هویت دو مرحله‌ای توکن خود را وارد کنید:",
"tokenValidSettingsMsg": "توکن شما معتبر است، هم اکنون میتوانید احراز هویت دو مرحله‌ای را فعال کنید.",
"confirmEnableTwoFAMsg": "آیا از فعال سازی احراز هویت دو مرحله‌ای مطمئن هستید؟",
"confirmDisableTwoFAMsg": "آیا از غیرفعال سازی احراز هویت دومرحله‌ای مطمئن هستید؟",
"Settings": "تنظیمات",
"Dashboard": "پیشخوان",
"New Update": "بروزرسانی جدید!",
"New Update": "بروزرسانی جدید",
"Language": "زبان",
"Appearance": "ظاهر",
"Theme": "پوسته",
@@ -48,13 +48,13 @@
"Status": "وضعیت",
"DateTime": "تاریخ و زمان",
"Message": "پیام",
"No important events": "رخداد جدیدی موجود نیست.",
"No important events": "رخداد جدیدی موجود نیست",
"Resume": "ادامه",
"Edit": "ویرایش",
"Delete": "حذف",
"Current": "فعلی",
"Uptime": "آپتایم",
"Cert Exp.": "تاریخ انقضای SSL",
"Cert Exp.": "تاریخ انقضای SSL.",
"day": "روز",
"-day": "-روز",
"hour": "ساعت",
@@ -76,7 +76,7 @@
"Accepted Status Codes": "وضعیت‌های (Status Code) های قابل قبول",
"Save": "ذخیره",
"Notifications": "اطلاع‌رسانی‌ها",
"Not available, please setup.": "هیچ موردی موجود نیست، اولین مورد را راه اندازی کنید!",
"Not available, please setup.": "هیچ موردی موجود نیست، اولین مورد را راه اندازی کنید.",
"Setup Notification": "راه اندازی اطلاع‌رسانی‌",
"Light": "روشن",
"Dark": "تاریک",
@@ -87,8 +87,8 @@
"None": "هیچ کدام",
"Timezone": "موقعیت زمانی",
"Search Engine Visibility": "قابلیت دسترسی برای موتورهای جستجو",
"Allow indexing": "اجازه ایندکس شدن را بده.",
"Discourage search engines from indexing site": "به موتورهای جستجو اجازه ایندکس کردن این سامانه را نده.",
"Allow indexing": "اجازه ایندکس شدن در موتور های جستجو را بده",
"Discourage search engines from indexing site": "به موتورهای جستجو اجازه ایندکس کردن این سامانه را نده",
"Change Password": "تغییر رمزعبور",
"Current Password": "رمزعبور فعلی",
"New Password": "رمزعبور جدید",
@@ -98,10 +98,10 @@
"Enable Auth": "فعال سازی تایید هویت",
"disableauth.message1": "آیا مطمئن هستید که میخواهید <strong>احراز هویت را غیر فعال کنید</strong>?",
"disableauth.message2": "این ویژگی برای کسانی است که <strong> لایه امنیتی شخص ثالث دیگر بر روی این آدرس فعال کرده‌اند</strong>، مانند Cloudflare Access.",
"Please use this option carefully!": "لطفا از این امکان با دقت استفاده کنید.",
"Please use this option carefully!": "لطفا از این امکان با دقت استفاده کنید!",
"Logout": "خروج",
"Leave": "منصرف شدم",
"I understand, please disable": "متوجه هستم، لطفا غیرفعال کنید!",
"I understand, please disable": "متوجه هستم، غیرفعال کن",
"Confirm": "تایید",
"Yes": "بلی",
"No": "خیر",
@@ -126,12 +126,12 @@
"Import": "ورود اطلاعات",
"respTime": "زمان پاسخگویی (میلی‌ثانیه)",
"notAvailableShort": "ناموجود",
"Default enabled": "به صورت پیش‌فرض فعال باشد.",
"Apply on all existing monitors": "بر روی تمامی مانیتور‌های فعلی اعمال شود.",
"Default enabled": "به صورت پیش‌فرض فعال باشد",
"Apply on all existing monitors": "بر روی تمامی مانیتور‌های فعلی اعمال شود",
"Create": "ایجاد",
"Clear Data": "پاکسازی داده‌ها",
"Events": "رخداد‌ها",
"Heartbeats": "Heartbeats",
"Heartbeats": "ضربان قلب",
"Auto Get": "Auto Get",
"backupDescription": "شما میتوانید تمامی مانیتورها و تنظیمات اطلاع‌رسانی‌ها را در قالب یه فایل JSON دریافت کنید.",
"backupDescription2": "البته تاریخچه رخدادها دراین فایل قرار نخواهند داشت.",
@@ -152,10 +152,10 @@
"Active": "فعال",
"Inactive": "غیرفعال",
"Token": "توکن",
"Show URI": "نمایش آدرس (URI) ",
"Show URI": "نمایش آدرس (URI)",
"Tags": "برچسب‌ها",
"Add New below or Select...": "یک مورد جدید اضافه کنید و یا از لیست انتخاب کنید…",
"Tag with this name already exist.": "یک برچسب با این «نام» از قبل وجود دارد",
"Tag with this name already exist.": "یک برچسب با این «نام» از قبل وجود دارد.",
"Tag with this value already exist.": "یک برچسب با این «مقدار» از قبل وجود دارد.",
"color": "رنگ",
"value (optional)": "مقدار (اختیاری)",
@@ -167,13 +167,13 @@
"Indigo": "نیلی",
"Purple": "بنفش",
"Pink": "صورتی",
"Search...": "جستجو...",
"Search...": "جستجو",
"Avg. Ping": "متوسط پینگ",
"Avg. Response": "متوسط زمان پاسخ",
"Entry Page": "صفحه ورودی",
"statusPageNothing": "چیزی اینجا نیست، لطفا یک گروه و یا یک مانیتور اضافه کنید!",
"statusPageNothing": "چیزی اینجا نیست، لطفا یک گروه و یا یک مانیتور اضافه کنید.",
"No Services": "هیچ سرویسی موجود نیست",
"All Systems Operational": "تمامی سیستم‌ها عملیاتی هستند!",
"All Systems Operational": "تمامی سیستم‌ها فعال هستند",
"Partially Degraded Service": "افت نسبی کیفیت سرویس",
"Degraded Service": "افت کامل کیفیت سرویس",
"Add Group": "اضافه کردن گروه",
@@ -187,7 +187,7 @@
"One record": "یک مورد",
"Info": "اطلاعات",
"Powered by": "نیرو گرفته از",
"apprise": "Apprise (Support 50+ Notification services)",
"apprise": "Apprise (پشتیبانی از 50+ خدمات اعلان)",
"Monitor": "مانیتور | مانتیور ها",
"Help": "کمک",
"Game": "بازی",
@@ -197,5 +197,561 @@
"statusMaintenance": "در دست تعمیر",
"Maintenance": "در حال تعمیر",
"General Monitor Type": "حالت مانیتور عمومی",
"markdownSupported": "شیوه نگارشی Markdown پشتیبانی می شود"
"markdownSupported": "شیوه نگارشی Markdown پشتیبانی می شود",
"Body Encoding": "انکودینگ محتوا",
"twilioFromNumber": "از شماره",
"twilioToNumber": "به شماره",
"Resend Notification if Down X times consecutively": "اگر X بار متوالی غیرفعال بود، مجددا اطلاع بده",
"successMessageExplanation": "پیام MQTT موفقیت آمیز به نظر نمیرسد",
"Create Incident": "یک حادثه را اطلاع دهید",
"Switch to Light Theme": "تغییر به حالت روشن",
"No monitors available.": "هیچ مانیتوری در دسترس نیست.",
"deleteProxyMsg": "آیا مطمئن هستید که میخواهید پروکسی را برای همه مانیتور ها غیرفعال کنید؟",
"enableProxyDescription": "این پروکسی تا زمانی که فعال نشود روی درخواست های مانیتور اثری نخواهد داشت. می‌توانید با توجه به وضعیت فعال‌سازی، پروکسی را از همه مانیتورها به طور موقت غیرفعال کنید.",
"supportTelegramChatID": "پشتیبانی از چت مستقیم / گروه / کانال",
"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) را می توان با کلیک بر روی نام پروفایل خود (پایین سمت چپ) و اسکرول کردن به پایین و سپس روی Create Token ایجاد کرد. ",
"A list of Notification Services can be found in Home Assistant under \"Developer Tools > Services\" search for \"notification\" to find your device/phone name.": "فهرستی از سرویس‌های اعلان را می‌توانید در هوم اسیستنت در قسمت «ابزارهای برنامه‌نویس > خدمات» برای «اعلان» جستجو کنید تا نام دستگاه/تلفن خود را پیدا کنید.",
"lastDay4": "چهارمین روز آخر ماه",
"dnsCacheDescription": "ممکن است در برخی از محیط های IPv6 کار نکند، اگر با مشکلی مواجه شدید آن را غیرفعال کنید.",
"Maintenance Time Window of a Day": "صفحه نگه داری در روز",
"Messaging API": "API پیام (Messaging API)",
"wayToGetLineChannelToken": "ابتدا به {0} دسترسی پیدا کنید، یک ارائه دهنده و کانال ایجاد کنید (API پیام)، سپس می توانید رمز توکن کانال و آیدی کاربری را از آیتم های منوی ذکر شده در بالا دریافت کنید.",
"aboutMattermostChannelName": "می‌توانید با وارد کردن نام کانال در قسمت «نام کانال»، کانال پیش‌فرضی را که وب هوک به آن پست می‌کند لغو کنید. این باید در تنظیمات Mattermost Webhook فعال شود. مثال: #other-channel",
"dnsPortDescription": "پورت سرور DNS. پیش فرض ۵۳. می توانید این عدد را در هر زمانی عوض کنید.",
"affectedStatusPages": "نمایش این پیام تعمیر و نگه داری در صفحات استاتوس انتخاب شده",
"octopushSMSSender": "نام فرستنده پیامک: 3-11 الفبای انگلیسی، حروف و فاصله (a-zA-Z0-9)",
"Lowcost": "کم هزینه",
"You can divide numbers with": "می توانید اعداد را با آن تقسیم کنید",
"goAlertInfo": "GoAlert یک برنامه اوپن سورس برای زمان‌بندی تماس، افزایش خودکار و اعلان‌ها (مانند پیامک یا تماس‌های صوتی) است. به طور خودکار شخص مناسب، راه درست و در زمان مناسب را درگیر کنید! {0}",
"API Keys": "کلید های API",
"Expiry": "انقضا",
"Expiry date": "انقضا در تاریخ",
"Don't expire": "بدون انقضا (منقضی نمی شود)",
"For safety, must use secret key": "برای امنیت، میببایستی از SecretKey استفاده کنید",
"promosmsTypeFlash": "SMS FLASH - پیام به طور خودکار در دستگاه گیرنده نشان داده می شود. فقط به گیرندگان لهستانی محدود می شود.",
"promosmsTypeFull": "SMS FULL - پیامک پریموم، می توانید از نام فرستنده خود استفاده کنید (ابتدا باید نام خود را ثبت کنید). قابل اعتماد برای هشدار.",
"matrixHomeserverURL": "URL هوم سرور (با http(s):// و پورت اختیاری)",
"matrixDesc1": "با مراجعه به بخش پیشرفته تنظیمات اتاق در کلاینت Matrix خود می توانید آیدی داخلی اتاق را بیابید. باید شبیه \"!QMdRCpUIfLwsfjxye6:home.server\" باشد.",
"wayToGetPagerDutyKey": "با رفتن به Service -> Service Directory -> (Select a Service) -> Integrations -> Add integration می توانید این مورد را دریافت کنید. در اینجا می توانید \"Events API V2\" را جستجو کنید. اطلاعات بیشتر در {0}",
"smseagleRecipientType": "نوع گیرنده",
"smseagleEncoding": "ارسال به صورت یونیکد",
"Leave blank to use a shared sender number.": "برای استفاده از شماره فرستنده مشترک، آن را خالی بگذارید.",
"onebotSafetyTips": "برای امنیت، میبایستی توکن دسترسی اضافه کنید",
"Custom Monitor Type": "نوع مانیتور سفارشی",
"apiKeyAddedMsg": "کلید API شما اضافه شده است. لطفاً آن را یادداشت کنید زیرا دیگر نمایش داده نخواهد شد.",
"deleteAPIKeyMsg": "آیا مطمئن هستید که می خواهید این کلید API را غیرفعال کنید؟",
"twilioAccountSID": "SID حساب",
"twilioAuthToken": "توکن اعتبارسنجی",
"appriseNotInstalled": "Apprise نصب نشده است. {0}",
"trustProxyDescription": "به هدرهای «X-Forwarded-*» اعتماد کن. اگر می‌خواهید IP مشتری صحیح را دریافت کنید و آپ‌تایم کومای شما پشت پروکسی مانند Nginx یا Apache قرار دارد، باید این گزینه را فعال کنید.",
"matrixDesc2": "اکیداً توصیه می‌شود که یک کاربر جدید ایجاد کنید و از رمز دسترسی کاربر Matrix خود استفاده نکنید زیرا امکان دسترسی کامل به حساب شما و تمام اتاق‌هایی را که به آنها ملحق شده‌اید می‌دهد. در عوض، یک کاربر جدید ایجاد کنید و فقط او را به اتاقی دعوت کنید که می‌خواهید اعلان را دریافت کنید. می‌توانید با اجرای {0} توکن دسترسی را دریافت کنید",
"Certificate Chain": "زنجیره گواهی (Certificate Chain)",
"telegramMessageThreadID": "(اختیاری) آیدی Thread پیام",
"telegramMessageThreadIDDescription": "(اختیاری) شناسه منحصر به فرد برای موضوع پیام هدف در انجمن. فقط برای سوپر گروه های انجمن",
"Channel Name": "نام کانال",
"auto acknowledged": "تصدیق خودکار",
"needPushEvery": "هر {0} ثانیه، URL زیر را صدا بزن.",
"pushOptionalParams": "پارامترهای اختیاری: {0}",
"Affected Monitors": "مانیتورهای تحت تأثیر",
"Pick Affected Monitors...": "انتخاب مانیتورهای تحت تأثیر…",
"Start of maintenance": "زمان شروع نگهداری",
"All Status Pages": "همه صفحات مشاهده وضعیت",
"Select status pages...": "انتخاب صفحه مشاهده وضعیت…",
"here": "اینجا",
"Required": "اجباری",
"Post URL": "URL بعدی",
"defaultNotificationName": "هشدار {notification} در ({number})",
"Add one": "اضافه کردن",
"Page Not Found": "صفحه درخواستی پیدا نشد",
"Reverse Proxy": "ریورس پروکسی",
"Backup": "پشتیبان گیری",
"API Key": "کلید API",
"Show update if available": "نمایش بروز رسانی اگر موجود بود",
"Check how to config it for WebSocket": "بررسی چگونگی پیکربندی برای وب سوکت",
"Steam Game Server": "سرور گیم استیم",
"Most likely causes:": "به احتمال زیاد بخاطر:",
"The resource is no longer available.": "منبع دیگر در دسترس نیست.",
"Docker Container": "کانتینر داکر",
"Container Name / ID": "نام / آیدی کانتینر",
"Docker Host": "هاست داکر",
"Docker Hosts": "هاست های داکر",
"Domain": "دامنه",
"Clone Monitor": "تکثیر",
"Clone": "تکثیر",
"cloneOf": "تکثیر {0}",
"Prefix Custom Message": "پیشوند پیام سفارشی",
"enableGRPCTls": "امکان ارسال درخواست gRPC با اتصال TLS",
"pushoversounds classical": "کلاسیک",
"smtpDkimSettings": "تنظیمات DKIM",
"aboutChannelName": "اگر می‌خواهید کانال وب هوک را دور بزنید، نام کانال را در قسمت {0} نام کانال وارد کنید. مثال: #other-channel",
"aboutKumaURL": "اگر قسمت URL آپ‌تایم کوما را خالی بگذارید، به طور پیش‌فرض به صفحه پروژه گیت هاب تبدیل می‌شود.",
"smtpDkimDesc": "لطفاً برای استفاده به Nodemailer DKIM {0} مراجعه کنید.",
"alertaApiEndpoint": "اند پوینت API",
"serwersmsAPIUser": "نام کاربری API (شامل پیشوند webapi_)",
"serwersmsAPIPassword": "رمز عبور API",
"serwersmsPhoneNumber": "شماره موبایل",
"serwersmsSenderName": "نام فرستنده پیامک (ثبت شده از طریق پورتال مشتری)",
"alertaRecoverState": "حالت ریکاور (Recover State)",
"smseagleToken": "توکن دسترسی API",
"Google Analytics ID": "آیدی گوگل آنالیتیکس",
"pagertreeLow": "کم",
"pagertreeMedium": "متوسط",
"pagertreeHigh": "زیاد",
"pagertreeCritical": "حساس - خیلی مهم",
"pagertreeIntegrationUrl": "URL یکپارچه سازی",
"pagertreeUrgency": "اهمیت",
"pagertreeSilent": "بی صدا",
"pagertreeResolve": "Resolve اتوماتیک",
"pagertreeDoNothing": "هیچ کاری نکن",
"wayToGetPagerTreeIntegrationURL": "پس از ایجاد ادغام آپ‌تایم کوما در PagerTree، اند پوینت را کپی کنید. مشاهده جزئیات کامل در {0}",
"telegramProtectContent": "محافظت از ارسال/ذخیره",
"telegramProtectContentDescription": "در صورت فعال بودن، پیام‌های ربات در تلگرام از ارسال و ذخیره محافظت می‌شوند.",
"wayToGetTelegramChatID": "برای مشاهده chat_id می توانید شناسه چت خود را با ارسال یک پیام به ربات و رفتن به این URL دریافت کنید:",
"YOUR BOT TOKEN HERE": "شناسه ربات خود را اینجا وارد کنید",
"chatIDNotFound": "شناسه چت یافت نشد. لطفا ابتدا به ربات پیام دهید",
"disableCloudflaredNoAuthMsg": "شما در حالت بدون احراز هویت هستید، رمز عبور در این حالت لازم نیست.",
"Trigger type:": "نوع راه اندازی:",
"DateTime Range": "محدوده تاریخ",
"loadingError": "نمی توان داده ها را دریافت کرد، لطفاً بعداً دوباره امتحان کنید.",
"High": "زیاد",
"Retry": "تلاش مجدد",
"Topic": "موضوع",
"Integration Key": "کلید یکپارچه سازی",
"Edit Tag": "ویرایش تگ",
"Server Address": "آدرس سرور",
"Learn More": "بیشتر بدانید",
"Customize": "شخصی سازی",
"Custom Footer": "فوتر اختصاصی",
"No Proxy": "بدون پروکسی",
"Authentication": "اعتبارسنجی",
"steamApiKeyDescription": "برای مانیتورینگ یک سرور استیم،‌ شما نیاز به یک \"Steam Web-API key\" دارید. برای دریافت کلید میتوانید از اینجا اقدام کنید: ",
"No Monitors": "بدون مانیتور",
"Untitled Group": "دسته بنده نشده",
"Services": "سرویس ها",
"Discard": "دست کشیدن",
"Cancel": "انصراف",
"About": "درباره آپ‌تایم کوما",
"wayToGetCloudflaredURL": "(دریافت Cloudflared از {0})",
"cloudflareWebsite": "وب سایت کلادفلر",
"shrinkDatabaseDescription": "تریگر VACUUM برای SQLite. اگر دیتابیس شما بعد از 1.10.0 ایجاد شده باشد، AUTO_VACUUM قبلاً فعال شده است و لازم نیست این عمل انجام شود. (Trigger database VACUUM for SQLite. If your database is created after 1.10.0, AUTO_VACUUM is already enabled and this action is not needed.).",
"Message:": "پیام:",
"HTTP Headers": "هدر های HTTP",
"Bot Token": "توکن بات",
"SecretKey": "کلید محرمانه (SecretKey)",
"telegramSendSilently": "ارسال بی صدا",
"telegramSendSilentlyDescription": "پیام را بی صدا ارسال کن. در این حالت کاربران یک اعلان بدون صدا دریافت خواهند کرد.",
"install": "نصب",
"Icon URL": "URL آیکون",
"Steam API Key": "کلید API استیم",
"Security": "امنیت",
"light": "روشن",
"Query": "کوئری",
"Effective Date Range": "محدوده تاریخ مورد تاثیر (اختیاری)",
"statusPageRefreshIn": "بارگذاری مجدد در هر: {0}",
"Content Type": "نوع محتوا (Content Type)",
"Server URL": "آدرس سرور",
"Priority": "اهمیت",
"emojiCheatSheet": "چیت شیت ایموجی ها: {0}",
"Read more": "بیشتر بدانید",
"webhookJsonDesc": "{0} برای هر HTTP سرور جدیدی مانند Express.js مناسب است",
"Method": "متد",
"Headers": "هدر ها",
"PushUrl": "URL پوش",
"HeadersInvalidFormat": "هدر ریکوئست یک JSON درست نیست: ",
"BodyInvalidFormat": "هدر ریکوئست یک JSON درست نیست: ",
"Monitor History": "گزارش مانیتورینگ",
"clearDataOlderThan": "گزارشات مانیتورینگ را برای {0} روز نگه دار.",
"PasswordsDoNotMatch": "رمز عبور وارد شده درست نیست.",
"topic": "موضوع",
"topicExplanation": "موضوع MQTT برای مانیتور",
"successMessage": "پیام موفقیت آمیز",
"recent": "اخیر",
"Done": "انجام شده",
"Shrink Database": "فشرده سازی دیتابیس",
"Pick a RR-Type...": "یک تایپ RR انتخاب کنید…",
"Pick Accepted Status Codes...": "یک استاتوس کد قابل قبول انتخاب کنید…",
"Default": "پیش فرض",
"HTTP Options": "آپشن های HTTP",
"Title": "عنوان",
"Content": "محتوا",
"primary": "اولیه",
"dark": "تیره",
"Post": "اطلاع بده",
"Please input title and content": "لطفا یک عنوان و محتوا وارد کنید",
"Created": "ساخته شده در",
"Last Updated": "ویرایش شده در",
"Unpin": "برداشتن",
"Switch to Dark Theme": "تغییر به حالت تیره",
"Show Tags": "نمایش تگ ها",
"Hide Tags": "مخفی سازی تگ ها",
"Description": "توضحیات",
"Custom CSS": "CSS اختصاصی",
"deleteStatusPageMsg": "آیا بابت حذف این استاتوس پیچ مطمئن هستید؟",
"Proxies": "پروکسی ها",
"appriseInstalled": "Apprise نصب شده است.",
"Body": "متن",
"Start": "شروع",
"Stop": "توقف",
"Add New Status Page": "افزودن صفحه استاتوس جدید",
"Slug": "لینک",
"Accept characters:": "کاراکتر های مورد تایید:",
"startOrEndWithOnly": "شروع یا پایان فقط با {0}",
"No consecutive dashes": "بدون خط تیره متوالی",
"Next": "بعدی",
"The slug is already taken. Please choose another slug.": "این لینک قبلا گرفته شده است. لطفا لینک دیگری را انتخاب کنید.",
"New Status Page": "صفحه استاتوس جدید",
"Don't know how to get the token? Please read the guide:": "نمی دانید توکن خود را چگونه دریافت کنید؟ لطفا این راهنما را بخوانید:",
"The current connection may be lost if you are currently connecting via Cloudflare Tunnel. Are you sure want to stop it? Type your current password to confirm it.": "اگر در حال حاضر از طریق تونل به کلادفلر متصل می شوید، ممکن است اتصال فعلی قطع شود. آیا مطمئن هستید که می خواهید کلادفلر را متوقف کنید؟ رمز عبور خود را برای تایید این دستور تایپ کنید.",
"Trust Proxy": "پروکسی مورد اعتماد",
"Other Software": "برنامه های دیگر",
"For example: nginx, Apache and Traefik.": "برای مثال: Nginx ،Apache و Traefik.",
"signedInDispDisabled": "اعتبارسنجی غیرفعال شده است.",
"RadiusCallingStationIdDescription": "شناسه دستگاه تماس گیرنده",
"Certificate Expiry Notification": "اطلاعیه انقضای گواهی",
"RadiusSecret": "کلید Radius",
"API Username": "نام کاربری API",
"Also check beta release": "همچنین برای نسخه های بتا نیز جستجو کن",
"Using a Reverse Proxy?": "استفاده از ریورس پروکسی؟",
"There might be a typing error in the address.": "ممکن است یک خطای تایپ در آدرس وجود داشته باشد.",
"What you can try:": "آنچه می توانید امتحان کنید:",
"Go back to the previous page.": "بازگشت به صفحه قبلی.",
"Coming Soon": "به زودی",
"Connection String": "رشته اتصال‌ (Connection String)",
"settingsCertificateExpiry": "انقضای گواهی TLS",
"certificationExpiryDescription": "مانیتور های HTTPS راه اندازی میشود زمانی که گواهی TLS منقضی شود در:",
"Retype the address.": "آدرس را دوباره تایپ کنید.",
"Setup Docker Host": "راه اندازی هاست داکر",
"Connection Type": "نوع اتصال",
"Docker Daemon": "Daemon داکر",
"deleteDockerHostMsg": "آیا مطمئن هستید که می خواهید این هاست داکر را برای همه مانیتورها حذف کنید؟",
"Workstation": "محل کار (Workstation)",
"Packet Size": "سایز پکت",
"wayToGetTelegramToken": "شما میتوانید توکن خود را از {0} دریافت کنید.",
"Chat ID": "آیدی چت",
"wayToGetLineNotifyToken": "می‌توانید یک توکن جهت دسترسی از {0} دریافت کنید",
"Examples": "مثال ها",
"Home Assistant URL": "URL هوم اسیستنت شما",
"Long-Lived Access Token": "توکن دسترسی طولانی مدت",
"Notification Service": "سرویس اطلاع رسانی",
"default: notify all devices": "پیش فرض: اطلاع به همه دستگاه ها",
"Automations can optionally be triggered in Home Assistant:": "اتوماسیون ها می توانند به صورت اختیاری در هوم اسیستنت فعال شوند:",
"Event type:": "نوع ایونت:",
"Event data:": "نوع دیتا:",
"Then choose an action, for example switch the scene to where an RGB light is red.": "سپس یک عمل را انتخاب کنید، برای مثال صحنه را به جایی که نور RGB قرمز است تغییر دهید.",
"Optional": "اختیاری",
"recurringInterval": "وقفه",
"Recurring": "مکرر",
"strategyManual": "فعال/غیرفعال سازی به صورت دستی",
"warningTimezone": "این از منطقه زمانی سرور استفاده می کند",
"weekdayShortMon": "دوشنبه",
"weekdayShortTue": "سه شنبه",
"weekdayShortWed": "چهارشنبه",
"weekdayShortThu": "پنجشنبه",
"weekdayShortFri": "جمعه",
"weekdayShortSat": "شنبه",
"weekdayShortSun": "یکشنبه",
"dayOfWeek": "روز های هفته",
"dayOfMonth": "روز های ماه",
"lastDay": "روز آخر",
"lastDay1": "روز آخر ماه",
"lastDay2": "دومین روز آخر ماه",
"lastDay3": "سومین روز آخر ماه",
"Enable": "فعال سازی",
"Single Maintenance Window": "تعمیر و نگه داری تک صفحه",
"Schedule Maintenance": "زمانبندی تعمیر و نگهداری",
"Date and Time": "زمان و تاریخ",
"plugin": "پلاگین | پلاگین ها",
"installing": "در حال نصب",
"uninstall": "حذف از نصب",
"uninstalling": "درحال حذف",
"confirmUninstallPlugin": "آیا مطمئن هستید که می خواهید این پلاگین را حذف از نصب کنید؟",
"notificationRegional": "منطقه ای",
"secureOptionNone": "None / STARTTLS (25, 587)",
"secureOptionTLS": "TLS (465)",
"Ignore TLS Error": "خطای TLS را نادیده بگیر",
"From Email": "از ایمیل",
"emailCustomSubject": "موضوع سفارشی",
"To Email": "به ایمیل",
"smtpBCC": "BCC",
"Discord Webhook URL": "URL وب هوک دیسکورد",
"Bot Display Name": "نام نمایشی ربات",
"Hello @everyone is...": "سلام {'@'} همه…",
"wayToGetTeamsURL": "می‌توانید نحوه ایجاد وب هوک را در {0} بیاموزید.",
"wayToGetZohoCliqURL": "می‌توانید نحوه ایجاد وب هوک را در {0} بیاموزید.",
"needSignalAPI": "شما باید یک Signal Client با REST API داشته باشید.",
"wayToCheckSignalURL": "برای مشاهده نحوه تنظیم آن می توانید این URL را بررسی کنید:",
"Number": "عدد",
"Recipients": "گیرندگان",
"Channel access token": "توکن دسترسی به کانال",
"Line Developers Console": "کنسول توسعه دهندگان لاین (Line Developers Console)",
"lineDevConsoleTo": "کنسول توسعه دهندگان لاین (Line Developers Console) - {0}",
"Basic Settings": "تنظیمات پایه",
"User ID": "آیدی کاربر",
"aboutIconURL": "می‌توانید پیوندی به یک عکس در \"URL آیکون \" ارائه دهید تا عکس نمایه پیش‌فرض را لغو کنید. اگر نماد Emoji تنظیم شده باشد، این مورد استفاده نخواهد شد.",
"dataRetentionTimeError": "دوره نگهداری باید 0 یا بیشتر باشد",
"wayToGetDiscordURL": "شما می توانید این را با رفتن به تنظیمات سرور -> ادغام -> مشاهده وب هوک -> وب هوک جدید (Settings -> Integrations -> View Webhooks -> New Webhook) دریافت کنید",
"infiniteRetention": "برای دوره بی نهایت 0 را وارد تنظیم کنید.",
"confirmDeleteTagMsg": "آیا مطمئن هستید که می خواهید این تگ را حذف کنید؟ مانیتورهای مرتبط با این تگ حذف نخواهند شد.",
"grpcMethodDescription": "نام روش تبدیل به فرمت cammelCase مانند sayHello، check و غیره.",
"deleteMaintenanceMsg": "آیا مطمئن هستید که می خواهید این تعمیر و نگهداری را حذف کنید؟",
"recurringIntervalMessage": "یکبار اجرا برای هر روز | یکبار اجرا در هر {0} روز",
"affectedMonitorsDescription": "مانیتورهایی را انتخاب کنید که تحت تأثیر تعمیر و نگهداری فعلی هستند",
"atLeastOneMonitor": "حداقل یک مانیتور مورد تاثیر را انتخاب کنید",
"octopushAPIKey": "\"کلید API\" از اعتبارنامه های HTTP API در کنترل پنل",
"octopushLogin": "\"ورود\" از اعتبار HTTP API در کنترل پنل",
"promosmsLogin": "نام ورود API",
"pushoversounds cashregister": "صندوق فروش",
"pushoversounds falling": "رها کردن",
"pushoversounds incoming": "ورودی",
"pushoversounds intermission": "وقفه",
"pushoversounds magic": "سحر آمیز",
"pushoversounds mechanical": "مکانیکی",
"pushoversounds pianobar": "پیانو بار",
"pushoversounds siren": "آژیر",
"pushoversounds spacealarm": "هشدار فضایی",
"pushoversounds gamelan": "گیم لن (Gamelan)",
"Current User": "کاربر فعلی",
"pushoversounds none": "بی صدا",
"pushoversounds tugboat": "قایق یدک کش",
"pushoversounds alien": "هشدار بیگانه (طولانی)",
"pushoversounds climb": "صعود (طولانی)",
"pushoversounds persistent": "پایدار (طولانی)",
"pushoversounds echo": "اکو (طولانی)",
"pushoversounds updown": "بالا پایین (طولانی)",
"pushoversounds vibrate": "فقط ویبره",
"pushyToken": "توکن دستگاه",
"GoogleChat": "Google Chat (فقط Google Workspace)",
"wayToGetKookBotToken": "یک برنامه ایجاد کنید و توکن ربات خود را از {0} دریافت کنید",
"User Key": "کلید کاربر",
"Message Title": "عنوان پیام",
"Notification Sound": "صدای اعلان",
"More info on:": "اطلاعات بیشتر در مورد: {0}",
"pushoverDesc1": "اولویت اضطراری (2) دارای وقفه پیش‌فرض 30 ثانیه بین تلاش‌های مجدد است و پس از 1 ساعت منقضی می‌شود.",
"pushoverDesc2": "اگر می‌خواهید اعلان‌ها را به دستگاه‌های مختلف ارسال کنید، قسمت دستگاه را پر کنید.",
"pushyAPIKey": "کلید Secret API",
"wayToGetKookGuildID": "«حالت توسعه‌دهنده» را در تنظیمات کوک روشن کنید و روی انجمن کلیک راست کنید تا شناسه آن را دریافت کنید",
"Guild ID": "گیلد آیدی (Guild ID)",
"SMS Type": "نوع پیامک",
"octopushTypePremium": "پرمیوم (سریع - پیشنهاد شده برای هشدار ها)",
"octopushTypeLowCost": "کم هزینه (آهسته - گاهی اوقات توسط اپراتور مسدود می شود)",
"checkPrice": "بررسی قیمت‌های {0} :",
"apiCredentials": "اطلاعات API",
"octopushLegacyHint": "آیا از نسخه قدیمی Octopush (1387-1400) استفاده می کنید یا از نسخه جدید؟",
"octopushPhoneNumber": "شماره تلفن (حالت بین المللی مانند 989121234567+) ",
"LunaSea Device ID": "شناسه دستگاه LunaSea",
"Apprise URL": "آدرس Apprise",
"Example:": "مثال: {0}",
"Read more:": "بیشتر بخوانید: {0}",
"Free Mobile User Identifier": "شناسه کاربری Free Mobile",
"Free Mobile API Key": "کلید API در Free Mobile",
"Enable TLS": "فعال کردن TLS",
"Proto Service Name": "نام Proto Service",
"Proto Method": "متد Proto",
"Proto Content": "محتوای Proto",
"Economy": "اقتصاد",
"high": "زیاد",
"SMSManager API Docs": "مستندات SMSManager API ",
"Gateway Type": "نوع Gateway",
"Base URL": "URL پایه",
"goAlertIntegrationKeyInfo": "کلید ادغام API عمومی را برای سرویس در این قالب دریافت کنید \"aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeee\" معمولاً مقدار پارامتر توکن URL کپی شده است.",
"AccessKeyId": "آیدی AccessKey",
"PhoneNumbers": "شماره های موبایل",
"TemplateCode": "کد تمپلیت",
"Sms template must contain parameters: ": "قالب پیامک باید دارای پارامترهای زیر باشد: ",
"Bark Endpoint": "اند پوینت Bark",
"Bark Group": "گروه Bark",
"Bark Sound": "صدای Bark",
"WebHookUrl": "آدرس وب هوک",
"Device Token": "توکن دستگاه",
"Platform": "پلتفرم",
"Check octopush prices": "بررسی قیمت های octopush {0}.",
"SendKey": "کلید ارسال (SendKey)",
"SecretAccessKey": "کلید دسترسی مخفی (AccessKey Secret)",
"SignName": "نام امضا (SignName)",
"Android": "اندروید",
"Huawei": "هواوی",
"WeCom Bot Key": "کلید ربات WeCom",
"Setup Proxy": "تنظیم پروکسی",
"Proxy Protocol": "پروتکل پروکسی",
"Proxy Server": "پروتکل سرور",
"promosmsTypeEco": "SMS ECO - ارزان اما کند و اغلب بارگذاری شده است. فقط به گیرندگان لهستانی محدود می شود.",
"promosmsTypeSpeed": "SPEED SMS - بالاترین اولویت در سیستم. بسیار سریع و قابل اعتماد اما پرهزینه (حدود دو برابر قیمت SMS FULL).",
"promosmsPhoneNumber": "شماره تلفن (برای گیرنده لهستانی می توانید کدهای منطقه را نادیده بگیرید)",
"promosmsSMSSender": "نام فرستنده پیامک: نام از پیش ثبت شده یا یکی از پیش فرض ها: InfoSMS، SMS Info، MaxSMS، INFO، SMS",
"promosmsAllowLongSMS": "اجازه برای پیامک طولانی",
"Feishu WebHookUrl": "آدرس وب هوک Feishu",
"Internal Room Id": "آیدی اتاق داخلی",
"Uptime Kuma URL": "URL آپ‌تایم کوما",
"signalImportant": "مهم: شما نمی توانید گروه ها و اعداد را در گیرندگان ترکیب کنید!",
"aboutWebhooks": "اطلاعات بیشتر درباره وب هوک در: {0}",
"documentation": "مستندات",
"smtpDkimDomain": "نام دامنه",
"smtpDkimHashAlgo": "الگوریتم رمزگذاری (اختیاری)",
"smtpDkimheaderFieldNames": "کلیدهای هدر برای امضا (اختیاری)",
"smtpDkimskipFields": "کلیدهای هدر برای عدم امضا (اختیاری)",
"Integration URL": "URL یکپارچه سازی",
"smtpDkimKeySelector": "انتخابگر کلید (SecretKey)",
"smtpDkimPrivateKey": "کلید محرمانه (Private Key)",
"socket": "سوکت",
"do nothing": "هیچ کاری نکن",
"auto resolve": "حل خودکار",
"alertaEnvironment": "محیط",
"alertaApiKey": "کلید API",
"alertaAlertState": "وضعیت هشدار",
"smseagleTo": "شماره تلفن(ها)",
"smseagleGroup": "نام(های) گروه دفترچه تلفن",
"smseagleContact": "نام(های) تماس دفترچه تلفن",
"smseagleRecipient": "گیرنده(های) (چند مورد باید با کاما از هم جدا شوند)",
"smseagleUrl": "URL دستگاه SMSEagle شما",
"smseaglePriority": "اولویت پیام (0-9، پیش فرض = 0)",
"Recipient Number": "شماره گیرنده",
"From Name/Number": "از نام/شماره",
"Octopush API Version": "نسخه Octopush API",
"ntfy Topic": "موضوع ntfy",
"onebotHttpAddress": "آدرس HTTP OneBot",
"onebotMessageType": "نوع پیام OneBot",
"onebotGroupMessage": "گروه",
"onebotPrivateMessage": "خصوصی",
"onebotUserOrGroupId": "آیدی گروه/کاربر",
"PushDeer Key": "کلید PushDeer",
"wayToGetClickSendSMSToken": "می‌توانید نام کاربری و کلید API را از {0} دریافت کنید.",
"Continue": "ادامه",
"Add Another": "افزودن یکی دیگر",
"Key Added": "کلید API اضافه شد",
"Add API Key": "افزودن کلید API",
"No API Keys": "بدون کلید API",
"apiKey-active": "فعال",
"apiKey-expired": "منقضی شده",
"apiKey-inactive": "غیرفعال",
"Expires": "انقضا",
"disableAPIKeyMsg": "آیا مطمئن هستید که می خواهید این کلید API را غیرفعال کنید؟",
"Generate": "ایجاد یک کلید API جدید",
"lunaseaTarget": "هدف",
"lunaseaDeviceID": "آيدی دستگاه",
"lunaseaUserID": "آیدی کاربر",
"Auto resolve or acknowledged": "حل خودکار یا اعلام اطلاع یافته (Auto resolve or acknowledged)",
"Legacy Octopush-DM": "(Legacy Octopush-DM)",
"smtpCC": "ارسال نسخه به",
"promosmsPassword": "رمز عبور API",
"pushoversounds pushover": "Pushover (پیش‌فرض)",
"pushoversounds bike": "دوچرخه",
"pushoversounds bugle": "بوق",
"pushoversounds cosmic": "کیهانی",
"resendEveryXTimes": "پیام را هر {0} بار دوباره ارسال کن",
"resendDisabled": "ارسال مجدد غیرفعال است",
"Push URL": "URL پوش",
"Schedule maintenance": "زمانبندی نگهداری (غیرفعال سازی دستی)",
"webhookFormDataDesc": "{multipart} برای PHP مناسب است. آرایه JSON نیاز است تا به این شکل باز شود {decodeFunction}",
"webhookAdditionalHeadersTitle": "هدر اضافی",
"webhookAdditionalHeadersDesc": "تنظیم هدر های اضافی که نیاز است با وب هوک ارسال شود.",
"Webhook URL": "آدرس وب هوک",
"Application Token": "توکن اپلیکیشن",
"Style": "حالت ها",
"info": "اطلاعات",
"warning": "هشدار",
"danger": "خطر",
"error": "خطا",
"critical": "اهمیت ویژه",
"HTTP Basic Auth": "حالت پایه احراز هویت (HTTP Basic Auth)",
"RadiusSecretDescription": "اشتراک گذاری Secret بین کاربر و سرور",
"RadiusCalledStationId": "نام Station Id",
"RadiusCalledStationIdDescription": "شناسه دستگاه فراخوانی شده",
"RadiusCallingStationId": "آیدی ایستگاه تماس (Calling Station Id)",
"tcp": "TCP / HTTP",
"Frontend Version": "نسخه فرانت اند",
"Frontend Version do not match backend version!": "نسخه فرانت اند با نسخه بک اند مطابقت ندارد!",
"backupOutdatedWarning": "منسوخ شده: از آنجایی که بسیاری از ویژگی ها اضافه شده اند و این ویژگی پشتیبان گیری کمی حفظ نشده است، نمی تواند یک نسخه پشتیبان کامل ایجاد یا بازیابی شود.",
"backupRecommend": "لطفاً مستقیماً از Volume یا پوشه داده (./data/) نسخه پشتیبان تهیه کنید.",
"No Maintenance": "بدون تعمیر و نگهداری",
"pauseMaintenanceMsg": "آیا مطمئن هستید که می خواهید توقف کنید؟",
"maintenanceStatus-under-maintenance": "تحت تعمیر و نگهداری",
"maintenanceStatus-inactive": "غیرفعال",
"maintenanceStatus-scheduled": "برنامه ریزی شده",
"maintenanceStatus-ended": "پایان یافته",
"maintenanceStatus-unknown": "ناشناخته",
"Display Timezone": "منطقه زمانی برای نمایش",
"Server Timezone": "منطقه زمانی در سرور",
"statusPageMaintenanceEndDate": "پایان",
"IconUrl": "URL آیکون",
"Enable DNS Cache": "فعال سازی کش DNS",
"Access Token": "توکن دسترسی",
"smtp": "ایمیل (SMTP)",
"Device": "دستگاه",
"Proxy server has authentication": "پروکسی سرور دارای اعتبارسنجی است",
"Add New Tag": "اضافه کردن تگ جدید",
"Custom": "غیره",
"default": "پیش فرض",
"enabled": "فعال",
"setAsDefault": "ذخیره به عنوان پیش فرض",
"proxyDescription": "پروکسی برای راه اندازی این مانیتور اجباری است.",
"setAsDefaultProxyDescription": "این پروکسی به طور پیش فرض برای مانیتورهای جدید فعال می شود. همچنان می توانید پروکسی را به طور جداگانه برای هر مانیتور غیرفعال کنید.",
"Valid": "درست",
"Invalid": "نادرست",
"User": "کاربر",
"Installed": "نصب شده",
"Not installed": "نصب نشده",
"Running": "در حال اجرا",
"Not running": "اجرا نشده",
"Remove Token": "حذف توکن",
"Please read": "لطفا بخوانید",
"Subject:": "موضوع:",
"Valid To:": "معتبر تا:",
"Days Remaining:": "روز های باقی مانده:",
"Fingerprint:": "اثرانگشت (Fingerprint):",
"No status pages": "بدون صفحات استاتوس",
"Domain Name Expiry Notification": "اعلان انقضای نام دامنه",
"Issuer:": "صادرکننده:",
"Date Created": "ایجاد شده در",
"Footer Text": "متن فوتر",
"Show Powered By": "نمایش قدرت گرفته از آپ‌تایم کوما",
"Domain Names": "نام دامنه ها",
"Proxy": "پروکسی",
"signedInDisp": "وارد شده به عنوان {0}",
"or": "یا",
"Disable": "غیرفعال سازی",
"endpoint": "نقطه پایانی",
"Status:": "وضعیت: {0}",
"Strategy": "استراتژی",
"Icon Emoji": "ایموجی آیکون",
"sameAsServerTimezone": "مشابه با منطقه زمانی سرور",
"startDateTime": "ساعت/روز شروع",
"endDateTime": "ساعت/روز پایان",
"cronSchedule": "برنامه زمانی: ",
"invalidCronExpression": "حالت کرون نامعتبر است: {0}",
"cronExpression": "حالت کرون",
"ntfyAuthenticationMethod": "روش اعتبارسنجی",
"ntfyUsernameAndPassword": "نام کاربری و رمز عبور",
"pushoverMessageTtl": "TTL پیام (ثانیه)",
"Show Clickable Link": "نمایش لینک های قابل کلیک",
"Open Badge Generator": "باز کردن نشان ساز (Badge Generator)",
"Badge Generator": "نشان ساز (Badge Generator) {0}",
"Badge Type": "نوع نشان",
"Badge Duration": "مدت نشان",
"Badge Label": "برچسب نشان",
"Badge Prefix": "پیشوند نشان",
"Badge Suffix": "پسوند نشان",
"Badge Label Color": "رنگ برچسب نشان",
"Badge Color": "رنگ نشان",
"Badge Label Prefix": "پیشوند برچسب نشان",
"Badge Label Suffix": "پسوند برچسب نشان",
"Badge Down Color": "رنگ نشان زمانی که مانیتور دچار قطعی و Down شده است",
"Badge Maintenance Color": "رنگ نشان برای زمانی که مانیتور در حالت نگهداری است",
"Badge Warn Color": "رنگ نشان زمانی که مانیتور در حالت هشدار است",
"Badge Down Days": "روز هایی که مانیتور دچار قطعی شده است",
"Badge Style": "حالت نشان",
"Badge value (For Testing only.)": "مقدار نشان (فقط برای تست.)",
"Badge URL": "آدرس نشان",
"Monitor Setting": "تنظیمات مانتیور {0}",
"Show Clickable Link Description": "اگر انتخاب شود، همه کسانی که به این صفحه وضعیت دسترسی دارند میتوانند به صفحه مانیتور نیز دسترسی داشته باشند.",
"Badge Up Color": "رنگ نشان زمانی که مانیتور بدون مشکل و بالا است",
"Badge Pending Color": "رنگ نشان زمانی که مانیتور در حال انتظار است",
"Badge Warn Days": "روزهایی که مانیتور در حالت هشدار است",
"noGroupMonitorMsg": "موجود نیست. ابتدا یک گروه مانیتور جدید ایجاد کنید.",
"Home": "خانه",
"Edit Maintenance": "ویرایش تعمیر و نگهداری",
"Cannot connect to the socket server": "عدم امکان ارتباط با سوکت سرور",
"Reconnecting...": "ارتباط مجدد...",
"Monitor Group": "گروه مانیتور",
"Group": "گروه",
"Close": "بستن"
}

View File

@@ -73,11 +73,11 @@
"Delete": "Supprimer",
"Current": "Actuellement",
"Uptime": "Disponibilité",
"Cert Exp.": "Expiration SSL.",
"Cert Exp.": "Expiration Cert SSL",
"day": "jour | jours",
"-day": "-jour",
"hour": "heure",
"-hour": "-heure",
"-hour": "heures",
"Response": "Temps de réponse",
"Ping": "Ping",
"Monitor Type": "Type de sonde",
@@ -88,8 +88,8 @@
"Port": "Port",
"Heartbeat Interval": "Intervalle de vérification",
"Retries": "Essais",
"Heartbeat Retry Interval": "Réessayer l'intervalle de vérification",
"Resend Notification if Down X times consecutively": "Renvoyer la notification si en panne X fois consécutivement",
"Heartbeat Retry Interval": "Intervalle de ré-essaie",
"Resend Notification if Down X times consecutively": "Renvoyer la notification si hors ligne X fois consécutivement",
"Advanced": "Avancé",
"Upside Down Mode": "Mode inversé",
"Max. Redirects": "Nombre maximum de redirections",
@@ -329,7 +329,7 @@
"Body": "Corps",
"Headers": "En-têtes",
"PushUrl": "URL Push",
"HeadersInvalidFormat": "Les en-têtes de la requête ne sont pas dans un format JSON valide : ",
"HeadersInvalidFormat": "Les en-têtes de la requête ne sont pas dans un format JSON valide : ",
"BodyInvalidFormat": "Le corps de la requête n'est pas dans un format JSON valide : ",
"Monitor History": "Historique de la sonde",
"clearDataOlderThan": "Conserver l'historique des données de la sonde durant {0} jours.",
@@ -338,7 +338,7 @@
"One record": "Un enregistrement",
"steamApiKeyDescription": "Pour surveiller un serveur Steam, vous avez besoin d'une clé Steam Web-API. Vous pouvez enregistrer votre clé ici : ",
"Current User": "Utilisateur actuel",
"topic": "Topic",
"topic": "Sujet",
"topicExplanation": "Topic MQTT à surveiller",
"successMessage": "Message de réussite",
"successMessageExplanation": "Message MQTT qui sera considéré comme un succès",
@@ -658,7 +658,7 @@
"dnsCacheDescription": "Il peut ne pas fonctionner dans certains environnements IPv6, désactivez-le si vous rencontrez des problèmes.",
"Single Maintenance Window": "Créneau de maintenance unique",
"Maintenance Time Window of a Day": "Créneau de la maintenance",
"Effective Date Range": "Plage de dates d'effet",
"Effective Date Range": "Plage de dates d'effet (facultatif)",
"Schedule Maintenance": "Créer une maintenance",
"Date and Time": "Date et heure",
"DateTime Range": "Plage de dates et d'heures",
@@ -699,7 +699,7 @@
"Edit Tag": "Modifier l'étiquette",
"Body Encoding": "Encodage du corps",
"telegramMessageThreadID": "(Facultatif) ID du fil de message",
"telegramMessageThreadIDDescription": "(Facultatif) Identifiant unique pour le fil de discussion cible (sujet) du forum; pour les supergroupes du forum uniquement",
"telegramMessageThreadIDDescription": "(Facultatif) Identifiant unique pour le fil de discussion ciblé (sujet) du forum; pour les supergroupes du forum uniquement",
"telegramProtectContent": "Protéger le transfert/l'enregistrement",
"telegramProtectContentDescription": "S'il est activé, les messages du robot dans Telegram seront protégés contre le transfert et l'enregistrement.",
"telegramSendSilently": "Envoyer silencieusement",
@@ -734,5 +734,55 @@
"pagertreeDoNothing": "Ne fais rien",
"pagertreeIntegrationUrl": "URL d'intégration",
"pagertreeCritical": "Critique",
"wayToGetPagerTreeIntegrationURL": "Après avoir créé l'intégration Uptime Kuma dans PagerTree, copiez le fichier Endpoint. Voir tous les détails {0}"
"wayToGetPagerTreeIntegrationURL": "Après avoir créé l'intégration Uptime Kuma dans PagerTree, copiez le fichier Endpoint. Voir tous les détails {0}",
"lunaseaDeviceID": "Identifiant de l'appareil",
"lunaseaUserID": "Identifiant de l'utilisateur",
"Add New Tag": "Ajouter une étiquette",
"lunaseaTarget": "Cible",
"statusPageRefreshIn": "Actualisation dans: {0}",
"twilioFromNumber": "Du Nombre",
"twilioToNumber": "Au Nombre",
"twilioAccountSID": "ID du compte",
"twilioAuthToken": "Jeton d'authentification",
"sameAsServerTimezone": "Identique au fuseau horaire du serveur",
"startDateTime": "Date/heure de début",
"endDateTime": "Date/heure de fin",
"cronExpression": "Expression cron",
"cronSchedule": "Calendrier: ",
"invalidCronExpression": "Expression Cron non valide : {0}",
"ntfyUsernameAndPassword": "Nom d'utilisateur et mot de passe",
"ntfyAuthenticationMethod": "Méthode d'authentification",
"pushoverMessageTtl": "TTL Message (Secondes)",
"Show Clickable Link": "Afficher le lien cliquable",
"Show Clickable Link Description": "Si cette case est cochée, tous ceux qui ont accès à cette page d'état peuvent accéder à l'URL du moniteur.",
"Open Badge Generator": "Ouvrir le générateur de badges",
"Badge Type": "Type de badge",
"Badge Duration": "Durée du badge",
"Badge Prefix": "Préfixe de badge",
"Badge Suffix": "Suffixe de badge",
"Badge Label Color": "Couleur de l'étiquette du badge",
"Badge Color": "Couleur du badge",
"Badge Label Prefix": "Préfixe d'étiquette de badge",
"Badge Label Suffix": "Suffixe d'étiquette de badge",
"Badge Up Color": "Couleur du badge en ligne",
"Badge Down Color": "Couleur du badge hors ligne",
"Badge Pending Color": "Couleur du badge en attente",
"Badge Maintenance Color": "Couleur du badge maintenance",
"Badge Warn Color": "Couleur du badge d'avertissement",
"Badge Warn Days": "Jours d'avertissement de badge",
"Badge Style": "Style de badge",
"Badge value (For Testing only.)": "Valeur du badge (Pour les tests uniquement.)",
"Monitor Setting": "Réglage de la sonde {0}",
"Badge Generator": "Générateur de badges {0}",
"Badge Label": "Étiquette de badge",
"Badge URL": "URL du badge",
"Cannot connect to the socket server": "Impossible de se connecter au serveur de socket",
"Reconnecting...": "Reconnexion...",
"Edit Maintenance": "Modifier la maintenance",
"Monitor Group": "Groupe de sonde | Groupe de sondes",
"Badge Down Days": "Badge hors ligne",
"Group": "Groupe",
"Home": "Accueil",
"noGroupMonitorMsg": "Pas disponible. Créez d'abord une sonde de groupe.",
"Close": "Fermer"
}

23
src/lang/gl.json Normal file
View File

@@ -0,0 +1,23 @@
{
"Settings": "Axustes",
"Dashboard": "Panel",
"Help": "Axuda",
"General": "Xeral",
"List": "Lista",
"Home": "Casa",
"Add": "Engadir",
"Up": "Arriba",
"Pending": "Pendente",
"statusMaintenance": "Mantemento",
"Maintenance": "Mantemento",
"Unknown": "Descoñecido",
"Reconnecting...": "Reconectando...",
"pauseDashboardHome": "Pausa",
"Pause": "Pausa",
"Name": "Nome",
"Status": "Estado",
"DateTime": "DataHora",
"Message": "Mensaxe",
"languageName": "Galego",
"Down": "Abaixo"
}

View File

@@ -724,5 +724,22 @@
"Edit Tag": "עריכת תגית",
"Learn More": "לקריאה נוספת",
"telegramSendSilently": "שליחה שקטה",
"telegramSendSilentlyDescription": "שליחת הודעות שקטה. משתמשים יקבלו ההתראה ללא צליל."
"telegramSendSilentlyDescription": "שליחת הודעות שקטה. משתמשים יקבלו ההתראה ללא צליל.",
"Add New Tag": "הוסף תג חדש",
"Home": "ראשי",
"sameAsServerTimezone": "אותו איזור זמן כמו השרת",
"cronSchedule": "לו\"ז: ",
"twilioToNumber": "למספר",
"startDateTime": "תאריך\\זמן התחלה",
"pagertreeSilent": "שקט",
"Reconnecting...": "מתחבר מחדש...",
"statusPageRefreshIn": "רענון תוך: {0}",
"Edit Maintenance": "ערוך תחזוקה",
"pagertreeUrgency": "דחיפות",
"pagertreeLow": "נמוכה",
"pagertreeMedium": "בינונית",
"pagertreeHigh": "גבוהה",
"pagertreeCritical": "קריטי",
"pagertreeResolve": "הגדרה אוטומטית",
"ntfyUsernameAndPassword": "שם משתמש וסיסמא"
}

43
src/lang/hi.json Normal file
View File

@@ -0,0 +1,43 @@
{
"Dashboard": "डैशबोर्ड",
"Help": "मदद",
"New Update": "नया अपडेट",
"Language": "भाषा",
"Appearance": "अपीयरेंस",
"Theme": "थीम",
"Game": "गेम",
"languageName": "हिंदी",
"Settings": "सेटिंग्स",
"General": "जनरल",
"List": "सूची",
"Add": "जोड़ें",
"Add New Monitor": "नया मॉनिटर जोड़ें",
"Pending": "लंबित",
"statusMaintenance": "रखरखाव",
"Maintenance": "रखरखाव",
"Unknown": "अज्ञात",
"Cannot connect to the socket server": "सॉकेट सर्वर से कनेक्ट नहीं हो सकता",
"pauseDashboardHome": "विराम",
"Resume": "फिर से शुरू करें",
"Delete": "हटाएं",
"Current": "मौजूदा",
"Up": "चालू",
"General Monitor Type": "सामान्य मॉनिटर प्रकार",
"Specific Monitor Type": "विशिष्ट मॉनिटर प्रकार",
"Pause": "विराम",
"Name": "नाम",
"Message": "संदेश",
"No important events": "कोई महत्वपूर्ण घटनाक्रम नहीं",
"Edit": "परिवर्तन",
"Ping": "पिंग",
"Monitor Type": "मॉनिटर प्रकार",
"Keyword": "कीवर्ड",
"Friendly Name": "दोस्ताना नाम",
"Version": "संस्करण",
"Home": "घर",
"Quick Stats": "शीघ्र आँकड़े",
"Reconnecting...": "पुनः कनेक्ट किया जा रहा है...",
"Down": "बंद",
"Passive Monitor Type": "निष्क्रिय मॉनिटर प्रकार",
"Status": "स्थिति"
}

View File

@@ -1,10 +1,10 @@
{
"languageName": "日本語",
"checkEverySecond": "{0}秒ごとにチェックします",
"retriesDescription": "サービスがダウンとしてマークされ、通知送信されるまでの最大リトライ数",
"retriesDescription": "サービスが完全に停止したと判断し、通知送信する前に再接続を試みる最大回数",
"ignoreTLSError": "HTTPS ウェブサイトの TLS/SSL エラーを無視する",
"upsideDownModeDescription": "ステータスの扱いを逆にします。サービスに到達可能な場合は、DOWNとなる。",
"maxRedirectDescription": "フォローするリダイレクトの最大数。リダイレクトを無効にするには0を設定する。",
"upsideDownModeDescription": "稼働ステータスを反転して扱います。サービスに接続可能な場合は、停止として扱います。",
"maxRedirectDescription": "必要な場合にリダイレクトする最大回数です。リダイレクトを無効にしたい場合は、0に設定してください。",
"acceptedStatusCodesDescription": "成功した応答とみなされるステータスコードを選択する。",
"passwordNotMatchMsg": "繰り返しのパスワードが一致しません。",
"notificationDescription": "監視を機能させるには、監視に通知を割り当ててください。",
@@ -21,15 +21,15 @@
"Language": "言語",
"Appearance": "外観",
"Theme": "テーマ",
"General": "全般",
"General": "全般",
"Version": "バージョン",
"Check Update On GitHub": "GitHubでアップデートを確認する",
"List": "一覧",
"Add": "追加",
"Add New Monitor": "監視の追加",
"Quick Stats": "統計",
"Up": "Up",
"Down": "Down",
"Up": "正常",
"Down": "停止",
"Pending": "中止",
"Unknown": "不明",
"Pause": "一時停止",
@@ -42,12 +42,12 @@
"Edit": "編集",
"Delete": "削除",
"Current": "現在",
"Uptime": "起動時間",
"Uptime": "稼働時間",
"Cert Exp.": "証明書有効期限",
"day": "日 | 日間",
"-day": "-日",
"hour": "時間",
"-hour": "-時間",
"-hour": "時間",
"Response": "レスポンス",
"Ping": "Ping",
"Monitor Type": "監視タイプ",
@@ -57,19 +57,19 @@
"Hostname": "ホスト名",
"Port": "ポート",
"Heartbeat Interval": "監視間隔",
"Retries": "Retries",
"Advanced": "Advanced",
"Upside Down Mode": "Upside Down Mode",
"Retries": "再試行回数",
"Advanced": "詳細設定",
"Upside Down Mode": "反転モード",
"Max. Redirects": "最大リダイレクト数",
"Accepted Status Codes": "正常なステータスコード",
"Save": "保存",
"Notifications": "通知",
"Not available, please setup.": "利用できません。設定してください。",
"Not available, please setup.": "利用できません。設定が必要です。",
"Setup Notification": "通知設定",
"Light": "Light",
"Dark": "Dark",
"Auto": "Auto",
"Theme - Heartbeat Bar": "Theme - Heartbeat Bar",
"Light": "ライト",
"Dark": "ダーク",
"Auto": "自動",
"Theme - Heartbeat Bar": "テーマ - 監視バー",
"Normal": "通常",
"Bottom": "下部",
"None": "なし",
@@ -120,7 +120,7 @@
"Also apply to existing monitors": "既存のモニターにも適用する",
"Export": "エクスポート",
"Import": "インポート",
"backupDescription": "すべての監視と通知方法をJSONファイルできます。",
"backupDescription": "すべての監視と通知設定をJSONファイルとしてバックアップすることができます。",
"backupDescription2": "※ 履歴と統計のデータはバックアップされません。",
"backupDescription3": "通知に使用するトークンなどの機密データも含まれています。注意して扱ってください。",
"alertNoFile": "インポートするファイルを選択してください。",
@@ -171,7 +171,7 @@
"Shrink Database": "データベースの縮小",
"Start": "始める",
"Retry": "リトライ",
"Please read": "読んでください",
"Please read": "次のリンクを参考にしてください",
"Orange": "橙",
"Gateway Type": "ゲートウェイの種類",
"Game": "ゲーム",
@@ -240,7 +240,7 @@
"Unpin": "ピンを外す",
"Switch to Light Theme": "ライトテーマに切り替える",
"Hide Tags": "タグを隠す",
"Description": "概要",
"Description": "メモ",
"Untitled Group": "名前の無いグループ",
"Services": "サービス",
"Discard": "破棄",
@@ -258,7 +258,7 @@
"proxyDescription": "プロキシはモニターに割り当てられていないと機能しません。",
"setAsDefaultProxyDescription": "このプロキシは、新しいモニターに対してデフォルトで有効になっています。モニターごとに個別にプロキシを無効にすることができます。",
"Remove Token": "Tokenを削除",
"Stop": "止める",
"Stop": "止",
"Add New Status Page": "新しいステータスページを追加",
"Next": "次へ",
"No Proxy": "プロキシなし",
@@ -500,7 +500,7 @@
"default: notify all devices": "デフォルト:すべてのデバイスに通知する",
"Trigger type:": "トリガータイプ:",
"Event data:": "イベントデータ:",
"backupOutdatedWarning": "非推奨:多くの機能が追加され、このバックアップ機能は少しメンテナンスされていないため、完全なバックアップの成や復元できません。",
"backupOutdatedWarning": "非推奨: 多くの機能に変更があり、バックアップ機能の開発が一部滞っているため、完全なバックアップの成や復元できません。",
"backupRecommend": "代わりにボリュームまたはデータフォルダ(./data/)を直接バックアップしてください。",
"recurringInterval": "インターバル",
"Recurring": "繰り返し",
@@ -510,5 +510,11 @@
"Android": "Android",
"Huawei": "Huawei",
"Device Token": "デバイストークン",
"recurringIntervalMessage": "毎日1回実行する{0} 日に1回実行する"
"recurringIntervalMessage": "毎日1回実行する{0} 日に1回実行する",
"Add New Tag": "新しいタグを追加",
"statusPageMaintenanceEndDate": "終了日",
"Body Encoding": "ボディエンコード",
"Learn More": "さらに詳しく",
"infiniteRetention": "保持期間を無制限にしたい場合は、0に設定してください。",
"Display Timezone": "表示タイムゾーン"
}

21
src/lang/ka.json Normal file
View File

@@ -0,0 +1,21 @@
{
"Dashboard": "დაფა",
"Help": "დახმარება",
"New Update": "განახლება",
"Language": "ენა",
"Appearance": "ვიზუალი",
"Theme": "სტილი",
"Game": "თამაში",
"Version": "ვერსია",
"Quick Stats": "თვალის გადავლება",
"Up": "მაღლა",
"Pending": "მოლოდინი",
"languageName": "Georgian",
"Settings": "კონფიგურაცია",
"General": "ძირითადი",
"Check Update On GitHub": "GitHub_ზე განახლების შემოწმება",
"List": "სია",
"Add": "დამატება",
"Add New Monitor": "ახალი მონიტორის დამატება",
"Down": "დაბლა"
}

View File

@@ -208,7 +208,7 @@
"smtpBCC": "숨은 참조",
"discord": "Discord",
"Discord Webhook URL": "Discord 웹훅 URL",
"wayToGetDiscordURL": "서버 설정 -> 연동 -> 웹후크 보기 -> 새 웹후크에서 얻을 수 있어요",
"wayToGetDiscordURL": "서버 설정 -> 연동 -> 웹 보기 -> 새 웹에서 얻을 수 있어요",
"Bot Display Name": "표시 이름",
"Prefix Custom Message": "접두사 메시지",
"Hello @everyone is...": "{'@'}everyone 서버 상태 알림이에요…",
@@ -660,7 +660,7 @@
"Disable": "비활성화",
"Single Maintenance Window": "단일 점검",
"Maintenance Time Window of a Day": "점검 시간",
"Effective Date Range": "유효 날짜 범위",
"Effective Date Range": "유효 날짜 범위 (옵션)",
"Schedule Maintenance": "점검 예약하기",
"Date and Time": "날짜 및 시간",
"DateTime Range": "날짜 시간 범위",
@@ -691,5 +691,63 @@
"webhookAdditionalHeadersTitle": "추가 헤더",
"webhookAdditionalHeadersDesc": "웹훅과 함께 전송될 추가 헤더를 설정해요.",
"HTTP Headers": "HTTP 헤더",
"Trust Proxy": "프록시 신뢰"
"Trust Proxy": "프록시 신뢰",
"API Keys": "API 키",
"markdownSupported": "Markdown 문법이 지원됨",
"telegramMessageThreadID": "(선택) 메시지 스레드 ID",
"Clone": "복제",
"cloneOf": "{0}의 복제본",
"Clone Monitor": "모니터링 복제",
"telegramProtectContent": "포워딩/저장 보호",
"telegramProtectContentDescription": "활성화 할경우 텔레그램 봇 메시지는 포워딩 및 저장으로부터 보호됩니다.",
"telegramSendSilentlyDescription": "조용히 메시지를 보냅니다. 사용자들은 무음으로 알림을 받습니다.",
"telegramSendSilently": "무음 알림",
"Add New Tag": "태그 추가",
"Edit Tag": "태그 수정",
"Server Address": "서버 주소",
"Learn More": "자세히 알아보기",
"Continue": "계속",
"Key Added": "키 추가됨",
"No API Keys": "API 키 없음",
"disableAPIKeyMsg": "이 API키를 정말로 비활성화하시겠습니까?",
"deleteAPIKeyMsg": "이 API키를 정말로 삭제하시겠습니까?",
"Generate": "생성",
"Body Encoding": "Body 인코딩",
"Expiry": "만료",
"Expiry date": "만료 날짜",
"Don't expire": "만료되지 않음",
"notificationRegional": "지역별",
"Google Analytics ID": "Google Analytics ID",
"Add API Key": "API 키 추가",
"apiKeyAddedMsg": "API 키가 추가되었습니다. 다시 표시되지 않을 것이므로 메모해 두세요.",
"pagertreeCritical": "긴급",
"apiKey-active": "사용 가능",
"lunaseaUserID": "사용자 ID",
"apiKey-expired": "만료됨",
"Expires": "만료일",
"twilioAuthToken": "인증 토큰",
"twilioFromNumber": "번호에서",
"twilioToNumber": "번호에서",
"twilioAccountSID": "계정 SID",
"pagertreeUrgency": "긴급",
"sameAsServerTimezone": "서버 시간대로 설정하기",
"startDateTime": "시작 시간",
"endDateTime": "종료 시간",
"cronExpression": "Cron 값",
"cronSchedule": "스케줄: ",
"invalidCronExpression": "알수없는 Cron 값입니다: {0}",
"Add Another": "다른 항목 추가",
"apiKey-inactive": "비활성화",
"pagertreeIntegrationUrl": "Integration 링크",
"pagertreeLow": "낮음",
"pagertreeMedium": "중간",
"pagertreeHigh": "높음",
"pagertreeResolve": "자동으로 해결하기",
"pagertreeDoNothing": "아무것도 하지 않음",
"wayToGetPagerTreeIntegrationURL": "PagerTree에서 Uptime Kuma 통합을 생성한 후 Endpoint를 복사합니다. 전체 세부 정보 보기 {0}",
"lunaseaTarget": "대상",
"lunaseaDeviceID": "기기 ID",
"statusPageRefreshIn": "{0} 후 새로고침",
"telegramMessageThreadIDDescription": "포럼의 대상 메시지 쓰레드(주제)에 대한 선택적 고유 식별인, 포럼 관리자 그룹에만 해당",
"pagertreeSilent": "없음"
}

View File

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

28
src/lang/ms.json Normal file
View File

@@ -0,0 +1,28 @@
{
"Help": "Bantuan",
"New Update": "Kemaskini baharu",
"Appearance": "Penampilan",
"Theme": "Tema",
"General": "Umum",
"Game": "Permainan",
"Primary Base URL": "URL Pangkalan Utama",
"Version": "Versi",
"Add": "Menambah",
"Quick Stats": "Statistik ringkas",
"Up": "Dalam talian",
"Down": "Luar talian",
"Pending": "Belum selesai",
"statusMaintenance": "Membaiki",
"Maintenance": "Membaiki",
"Unknown": "Tidak ketahui",
"General Monitor Type": "Jenis monitor umum",
"Check Update On GitHub": "Semak kemas kini dalam GitHub",
"List": "Senarai",
"Specific Monitor Type": "Jenis monitor spesifik",
"markdownSupported": "Sintaks markdown disokong",
"languageName": "Bahasa inggeris",
"Dashboard": "Papan pemuka",
"Language": "Bahasa",
"Add New Monitor": "Tambah monitor baharu",
"Passive Monitor Type": "Jenis monitor pasif"
}

View File

@@ -696,5 +696,12 @@
"markdownSupported": "Markdown syntax ondersteund",
"Resend Notification if Down X times consecutively": "Melding x keer opnieuw sturen als monitor offline is",
"loadingError": "Kan de data niet ophalen, probeer het later opnieuw.",
"smseagleContact": "Telefoonboek contact namen"
"smseagleContact": "Telefoonboek contact namen",
"apiKey-active": "Actief",
"apiKey-expired": "Verlopen",
"pagertreeLow": "Laag",
"pagertreeHigh": "Hoog",
"Clone": "Dupliceer",
"cloneOf": "Duplicaat van {0}",
"Add New Tag": "Voeg nieuw label toe"
}

View File

@@ -536,11 +536,11 @@
"pushoversounds cosmic": "Kosmiczny",
"pushoversounds falling": "Spadek",
"pushoversounds gamelan": "Gamelan",
"pushoversounds incoming": "Incoming",
"pushoversounds intermission": "Intermission",
"pushoversounds incoming": "Przychodzące",
"pushoversounds intermission": "Przerwa",
"pushoversounds magic": "Magia",
"pushoversounds mechanical": "Mechaniczny",
"pushoversounds pianobar": "Piano Bar",
"pushoversounds pianobar": "fortepianowy klawisz",
"pushoversounds siren": "Syrena",
"pushoversounds spacealarm": "Alarm kosmiczny",
"pushoversounds tugboat": "Holownik",
@@ -608,7 +608,7 @@
"backupRecommend": "Zamiast tego należy wykonać bezpośrednią kopię zapasową woluminu lub folderu danych (./data/).",
"Optional": "Opcjonalne",
"squadcast": "Squadcast",
"SendKey": "SendKey",
"SendKey": "Przycisk Wyślij",
"SMSManager API Docs": "Dokumentacja API SMSManager ",
"Gateway Type": "Typ bramy",
"SMSManager": "SMSManager",
@@ -663,7 +663,7 @@
"IconUrl": "URL ikony",
"Enable DNS Cache": "Włącz pamięć podręczną DNS",
"Single Maintenance Window": "Pojedyncze okno konserwacji",
"Effective Date Range": "Zakres dat obowiązywania",
"Effective Date Range": "Zakres dat obowiązywania (opcjonalnie)",
"Schedule Maintenance": "Planowanie konserwacji",
"DateTime Range": "Zakres czasowy",
"Maintenance Time Window of a Day": "Okno czasowe konserwacji na dzień",
@@ -727,5 +727,37 @@
"Key Added": "Klucz dodany",
"pagertreeDoNothing": "Nie rób nic",
"wayToGetPagerTreeIntegrationURL": "Po utworzeniu integracji Uptime Kuma w PagerTree, należy skopiować Endpoint. Zobacz pełne szczegóły {0}",
"notificationRegional": "Regionalne"
"notificationRegional": "Regionalne",
"twilioFromNumber": "Z numeru",
"twilioToNumber": "Do numeru",
"lunaseaTarget": "Cel",
"twilioAccountSID": "SID konta",
"twilioAuthToken": "Token autoryzacyjny",
"apiKeyAddedMsg": "Twój klucz API został dodany. Prosimy o zanotowanie go, ponieważ nie będzie on już więcej pokazywany.",
"telegramMessageThreadID": "(Opcjonalne) ID wątku wiadomości",
"telegramMessageThreadIDDescription": "Opcjonalny Unikalny identyfikator dla docelowego wątku wiadomości (tematu) forum; tylko dla supergrup forum",
"telegramProtectContent": "Ochrona przekazywania/zapisywania",
"telegramProtectContentDescription": "Jeśli włączona, wiadomości bota w Telegramie będą chronione przed przekazywaniem i zapisywaniem.",
"telegramSendSilently": "Wyślij po cichu",
"telegramSendSilentlyDescription": "Wysyła wiadomość po cichu. Użytkownicy otrzymają powiadomienie bez dźwięku.",
"statusPageRefreshIn": "Odświeżenie w ciągu: {0}",
"lunaseaDeviceID": "ID urządzenia",
"lunaseaUserID": "ID użytkownika",
"Add New Tag": "Dodaj nowy tag",
"startDateTime": "Data/godzina rozpoczęcia",
"cronSchedule": "Harmonogram: ",
"invalidCronExpression": "Nieprawidłowe sformułowanie Cron: {0}",
"sameAsServerTimezone": "Tak jak strefa czasowa serwera",
"endDateTime": "Data/godzina zakończenia",
"cronExpression": "Wyrażenie Cron",
"ntfyAuthenticationMethod": "Metoda Uwierzytelnienia",
"ntfyUsernameAndPassword": "Nazwa użytkownika i hasło",
"noGroupMonitorMsg": "Niedostępna. Stwórz najpierw grupę monitorów.",
"Close": "Zamknij",
"pushoverMessageTtl": "TTL wiadomości (sekundy)",
"Home": "Strona główna",
"Group": "Grupa",
"Monitor Group": "Grupa monitora",
"Reconnecting...": "Ponowne łączenie...",
"Cannot connect to the socket server": "Nie można połączyć się z serwerem gniazda"
}

Some files were not shown because too many files have changed in this diff Show More