Compare commits

...

239 Commits
1.5.0 ... 1.6.2

Author SHA1 Message Date
LouisLam
35fe9690e8 update to 1.6.2 2021-09-14 19:34:54 +08:00
LouisLam
ad2062713c fix pi4 on docker 2021-09-14 19:04:56 +08:00
LouisLam
d953ba7c60 update to 1.6.1 2021-09-11 03:12:25 +08:00
LouisLam
ef1604675b change to node:14-bullseye-slim, and reduce the docker image size 2021-09-11 02:50:29 +08:00
LouisLam
201bba63d7 add a healthcheck comment 2021-09-10 21:17:20 +08:00
LouisLam
31b4a5c33e build-docker, debian first 2021-09-10 18:08:23 +08:00
LouisLam
a49df29a87 update to 1.6.0 2021-09-10 17:56:33 +08:00
LouisLam
08de0090dc add a better approach for patching db, change setting.value from varchar to TEXT, restore Database.close() to 1.2.0 2021-09-10 17:23:45 +08:00
Louis Lam
97df200d6c Merge pull request #365 from Ponkhy/master
Minor translation fixes
2021-09-10 11:46:20 +08:00
Louis Lam
d5b17a3778 Merge pull request #367 from xinac721/master
update Simplified Chinese language(更新简体中文语言)
2021-09-10 11:45:57 +08:00
新逸Cary
2f0d994c83 update Simplified Chinese language(更新简体中文语言) 2021-09-10 09:26:01 +08:00
新逸Cary
e683033f4e Merge pull request #2 from louislam/master
update
2021-09-10 08:52:38 +08:00
Ponkhy
c62732fa9c Minor translation fixes 2021-09-10 01:24:21 +02:00
Louis Lam
2583dbe0e0 Update README.md 2021-09-09 22:58:31 +08:00
LouisLam
ca479673e7 avoid Chrome duplicate id warning 2021-09-09 21:28:27 +08:00
LouisLam
573c7faddd switch on the notification, if it is added in EditMonitor.vue 2021-09-09 21:24:29 +08:00
LouisLam
7a4432de1e better switch button color in dark mode 2021-09-09 20:45:58 +08:00
LouisLam
94a6c1a344 update language files 2021-09-09 20:38:19 +08:00
Louis Lam
ee9e48fe53 Merge pull request #361 from iomataani/italian-translation-update
Italian translation update
2021-09-09 20:26:07 +08:00
LouisLam
a4aee49b03 Merge remote-tracking branch 'origin/master' 2021-09-09 20:22:44 +08:00
LouisLam
e330875c80 fix restore fail if no isDefault 2021-09-09 20:22:32 +08:00
Paride Barison
44eba70b78 altri errorini sistemati 2021-09-09 14:19:15 +02:00
Paride Barison
c6dab944d1 altra correzione 2021-09-09 14:12:30 +02:00
Ioma Taani
0f4bc5850b Merge branch 'louislam:master' into italian-translation-update 2021-09-09 14:05:00 +02:00
Louis Lam
f37190a73d Update README.md 2021-09-09 18:02:58 +08:00
Louis Lam
29f96fae93 Update README.md 2021-09-09 18:01:15 +08:00
Louis Lam
331ae5ec20 Merge pull request #306 from Ponkhy/import-export
Added import and export function
2021-09-09 16:15:48 +08:00
LouisLam
8ee34c7904 also handle the first beat problem in uptime 2021-09-09 15:55:34 +08:00
LouisLam
4f07c2ea9a uptime calculation fully sum in sql 2021-09-09 15:46:28 +08:00
LouisLam
0b1d0d22ff add indexes, improve queries 2021-09-09 12:43:18 +08:00
LouisLam
24facc79d7 sendHeartbeatList use raw data 2021-09-09 11:57:23 +08:00
LouisLam
9f9c1007d7 increase sqlite cache size from 2MB to 12MB 2021-09-09 11:56:42 +08:00
LouisLam
70a6f0313c update .dockerignore 2021-09-09 11:41:43 +08:00
LouisLam
1d05df6ec9 [docker healthcheck] use retries instead, longer start-period causes starting problem in Docker Swarm mode 2021-09-09 11:41:28 +08:00
Ioma Taani
b54bbc302d migliorata la traduzione 2021-09-08 21:34:48 +02:00
LouisLam
dd283423ab improve smtp with cc, bbc and ignore tls 2021-09-09 01:13:09 +08:00
LouisLam
6006038689 fix monitor.stop() in some cases 2021-09-08 20:00:16 +08:00
LouisLam
a7b50c3630 Revert "add user-monitor-list"
This reverts commit 230a9bfaf9.
2021-09-08 19:54:38 +08:00
LouisLam
0ddbac5109 Revert "move userMonitorList out of server.js"
This reverts commit a7cf14c663.
2021-09-08 19:54:38 +08:00
LouisLam
0f440596c8 Revert "cache last heartbeat list in memory"
This reverts commit 87678ea92d.
2021-09-08 19:54:37 +08:00
LouisLam
5a0fcebd6e update link style 2021-09-08 18:59:30 +08:00
LouisLam
87678ea92d cache last heartbeat list in memory 2021-09-08 18:58:02 +08:00
LouisLam
0bf0cfa104 Merge remote-tracking branch 'origin/master' 2021-09-08 18:56:43 +08:00
LouisLam
a7cf14c663 move userMonitorList out of server.js 2021-09-08 16:27:37 +08:00
Louis Lam
325ce72a71 Merge pull request #352 from iomataani/italian-language
Italian language
2021-09-08 16:12:44 +08:00
Louis Lam
9d00bd9029 Merge pull request #357 from Saibamen/nav_from_dashHome
Route to monitor on `DashboardHome`
2021-09-08 16:11:39 +08:00
Adam Stachowicz
f6a168282e Route to monitor on DashboardHome 2021-09-08 09:55:08 +02:00
Ponkhy
acf6f1883b Fixed missing spaces in backup file 2021-09-08 09:02:24 +02:00
Paride Barison
f44f951e40 Tradotte le ultime cose come richiesto 2021-09-08 08:41:19 +02:00
Ioma Taani
dea9b05d49 Merge branch 'louislam:master' into italian-language 2021-09-08 08:30:10 +02:00
LouisLam
230a9bfaf9 add user-monitor-list 2021-09-08 14:16:30 +08:00
Louis Lam
d761d54d0e Merge pull request #340 from Ponkhy/default-notification
Added the option for default notifications
2021-09-08 12:49:14 +08:00
LouisLam
e37621853e Merge branch 'estonian'
# Conflicts:
#	src/pages/Settings.vue
2021-09-08 11:00:19 +08:00
LouisLam
f2cec60481 minor 2021-09-08 10:48:03 +08:00
LouisLam
2212cf9c08 fix confirmDisableAuth no msg if not tranlated 2021-09-08 10:28:40 +08:00
LouisLam
f7562e00c1 improve notification component, no double template 2021-09-08 10:25:03 +08:00
Ponkhy
678e248966 Added indicator while changing database records 2021-09-08 01:10:19 +02:00
Ponkhy
c1aebef005 Fixed SQL error due notification setup 2021-09-08 01:03:24 +02:00
Ponkhy
1ef4562905 Paused monitors stay paused after import 2021-09-07 23:32:25 +02:00
Ponkhy
69a7c4dab4 Improved importBackup handling 2021-09-07 23:14:44 +02:00
jtagcat
2c3880a326 Added Estonian.
- "Avg.": using ≈, as it's commonly enough known.
   There is no known shorthand for 'average' that I know of.

- Leaved some strings empty, as repeating the same words is useless, just noise.
2021-09-07 23:31:48 +03:00
Ponkhy
f4e885982d Added export/import description for de-DE 2021-09-07 21:10:47 +02:00
Ioma Taani
dcf6dd8b32 Ultimi aggiustamenti 2021-09-07 19:16:29 +02:00
Ioma Taani
093cfec8dd Typo. 2021-09-07 19:13:04 +02:00
Ioma Taani
f13dcb3c5b Update it-IT.js
Completed.
2021-09-07 19:07:50 +02:00
LouisLam
1a5ffd4755 add description for export/import 2021-09-08 00:46:48 +08:00
Ioma Taani
a2f9e9e9c4 Creata lingua italiana 2021-09-07 18:25:24 +02:00
LouisLam
62712f5cc4 Merge branch 'master' into import-export 2021-09-08 00:18:43 +08:00
LouisLam
f0f1847ad8 update language files 2021-09-07 23:53:20 +08:00
LouisLam
0aeaf87f5b Merge branch 'master' into default-notification
# Conflicts:
#	server/notification.js
#	src/components/NotificationDialog.vue
2021-09-07 23:40:42 +08:00
LouisLam
5ca0dd628d remove debug msg 2021-09-07 23:36:55 +08:00
LouisLam
4a106431f3 convert Telegram into a vue components 2021-09-07 23:32:05 +08:00
LouisLam
d164b6ccce prevent Chrome ask for saving password for notification settings (change to one-time-code to solve it) 2021-09-07 23:06:49 +08:00
LouisLam
da74391c3e convert notifications into modules 2021-09-07 22:42:46 +08:00
Louis Lam
850563442a Merge pull request #349 from Ponkhy/redirect-after-setup
Auto login after setup
2021-09-07 21:16:54 +08:00
Ponkhy
242e494cb5 Merge branch 'master' into import-export 2021-09-07 14:12:53 +02:00
Ponkhy
4faa409027 Merge branch 'master' into default-notification 2021-09-07 14:07:50 +02:00
Louis Lam
f842fb409e Update README.md 2021-09-07 17:44:51 +08:00
Louis Lam
72dd894856 Update README.md 2021-09-07 17:44:39 +08:00
LouisLam
0b0417e064 Merge remote-tracking branch 'origin/master' 2021-09-07 17:40:36 +08:00
LouisLam
fe35d95441 merge language files 2021-09-07 17:38:04 +08:00
LouisLam
da131a5156 Merge branch 'master' into clear-monitor-data
# Conflicts:
#	src/languages/da-DK.js
#	src/languages/en.js
#	src/languages/es-ES.js
#	src/languages/fr-FR.js
#	src/languages/ja.js
#	src/languages/ko-KR.js
#	src/languages/nl-NL.js
#	src/languages/ru-RU.js
#	src/languages/sr-latn.js
#	src/languages/sr.js
#	src/languages/sv-SE.js
#	src/languages/zh-CN.js
#	src/languages/zh-HK.js
2021-09-07 17:36:37 +08:00
Louis Lam
926c15ea40 Update healthcheck.js 2021-09-07 17:30:50 +08:00
LouisLam
87e874406a Merge branch 'Ponkhy_master_hidden' 2021-09-07 17:09:24 +08:00
LouisLam
0e288ea92d disable chrome save password dialog for autocompelete = off only 2021-09-07 17:06:33 +08:00
LouisLam
8a4a87716f disable chrome save password dialog 2021-09-07 16:46:42 +08:00
Ponkhy
d01fa9bcfc Auto login after setup 2021-09-07 10:25:25 +02:00
LouisLam
ec8c1cad31 update healthcheck.js for changing ssl cert / hostname / port 2021-09-07 16:06:28 +08:00
LouisLam
fb0fa2a843 add sqlite cli to docker 2021-09-07 15:36:29 +08:00
LouisLam
f5c6d422d5 switch debian to be the main docker base 2021-09-07 15:31:32 +08:00
Louis Lam
ea4240455f Merge pull request #347 from gaby/refactor-docker
Debian Dockerfile refactoring and improvements
2021-09-07 15:21:19 +08:00
Juan Calderon-Perez
ce30ee74e9 Revert changes breaking ARM releases 2021-09-07 01:26:42 -04:00
Juan Calderon-Perez
66b5f157eb Update Dockerfile order to improve layer caching. 2021-09-07 00:13:07 -04:00
Juan Calderon-Perez
5fe8a0917a Refactor Debian Dockerfile 2021-09-06 22:20:12 -04:00
Ponkhy
848296b77a Changed input for sensitive data to HiddenInput 2021-09-07 02:30:53 +02:00
Ponkhy
edfaacbb5f Added component HiddenInput.vue 2021-09-07 02:24:02 +02:00
Ponkhy
bb8385d690 Merge branch 'default-notification' of https://github.com/Ponkhy/uptime-kuma into default-notification 2021-09-06 09:46:36 +02:00
Ponkhy
b95404b6e0 Fixed suggestions 2021-09-06 09:45:52 +02:00
Louis Lam
cc351b2883 Merge pull request #339 from Minvinea/master
Update few words and file name
2021-09-06 13:38:26 +08:00
Louis Lam
3c06f249ca Merge pull request #338 from Saibamen/pl_fixes
[LANG] `pl` fixes
2021-09-06 12:44:56 +08:00
Minvinea
31648dc5e8 Update #2 2021-09-05 23:41:57 +02:00
Minvinea
1197cfa11e Update file name 2021-09-05 23:37:26 +02:00
Ponkhy
fd8c95d64e Merge branch 'master' into default-notification 2021-09-05 23:32:31 +02:00
Ponkhy
58240aceef Added the option for default notifications 2021-09-05 23:23:06 +02:00
Adam Stachowicz
e8b814733d Fix typo 2021-09-05 23:14:56 +02:00
Minvinea
dcc7799108 Update few words and name of file
https://prnt.sc/1rbosiy
2021-09-05 21:34:32 +02:00
Adam Stachowicz
424f20f8e1 Better upsideDownModeDescription 2021-09-05 20:59:04 +02:00
Adam Stachowicz
d4ff5d8b32 [LANG] pl fixes 2021-09-05 20:55:25 +02:00
LouisLam
899b33b3a9 add language missing keys 2021-09-06 01:31:05 +08:00
Ponkhy
1b8b33c4c3 Added set language to the Setup.vue 2021-09-05 15:40:35 +02:00
Louis Lam
d34ed00841 Merge pull request #333 from Misly16/master
Add Polish Locale
2021-09-05 12:03:07 +08:00
Louis Lam
f9c177b150 Merge pull request #315 from tgxn/patch-1
Notifications Content Improvements: SMTP, LunaSea, PushBullet
2021-09-05 11:52:50 +08:00
Misly
9952463350 Add Polish Locale 2021-09-04 20:38:53 +00:00
LouisLam
5837c353b7 change the default theme to auto from light 2021-09-05 02:47:31 +08:00
LouisLam
d5b32ffbb8 add language missing keys 2021-09-05 02:25:40 +08:00
LouisLam
61936ae5eb Merge remote-tracking branch 'Ponkhy/clear-monitor-data' into clear-monitor-data 2021-09-05 02:04:48 +08:00
LouisLam
299506ce45 reset the heartbeat list instead of reload the page after cleared events or heartbeats 2021-09-05 02:03:40 +08:00
LouisLam
ca8d4ab61b Merge branch 'master' into clear-monitor-data 2021-09-05 00:24:09 +08:00
LouisLam
ffbdf97478 improve heartbeat bar rendering in different dpi 2021-09-05 00:08:24 +08:00
LouisLam
cc25787878 improve heartbeat bar rendering in different dpi 2021-09-04 23:32:46 +08:00
Louis Lam
17453a8812 Merge pull request #332 from LeviSnoot/master
Update & Improve Swedish Translation
2021-09-04 23:16:01 +08:00
Levi
f1a151b4a1 Update & Improve Swedish Translation 2021-09-04 16:03:34 +02:00
Levi
d35b205fcc Update & Improve Swedish Translation 2021-09-04 16:01:41 +02:00
Levi
2a34e41d8c Improved Swedish translation 2021-09-04 15:59:49 +02:00
Domenic Horner
41d32bb9dd Undo parm changes 2021-09-04 20:08:18 +08:00
Ponkhy
0b9e410ea5 Added i18n key to en.js 2021-09-04 13:36:08 +02:00
Ponkhy
904ed326ca Merge branch 'louislam:master' into clear-monitor-data 2021-09-04 13:34:29 +02:00
LouisLam
778995a4fb add dockerfile-debian 2021-09-04 15:48:25 +08:00
LouisLam
ee60d74910 fix dockerfile path 2021-09-04 15:32:55 +08:00
LouisLam
6a603203cc update to 1.5.3 2021-09-04 15:07:32 +08:00
LouisLam
4b8e7fcffc update "build-docker" to build both platforms 2021-09-04 15:03:26 +08:00
LouisLam
7fd12f5485 rename nl_NL.js to nl-NL.js 2021-09-04 14:42:55 +08:00
LouisLam
0d87a6dfea update vue to 3.2.8 2021-09-04 14:41:45 +08:00
Domenic Horner
b0acda52f9 Add time to smtp body content 2021-09-04 11:27:18 +08:00
Domenic Horner
e9cd9be03a Use constants for UP/DOWN through notifications class 2021-09-04 11:09:34 +08:00
Domenic Horner
6ae279c7f3 Move title generation to notification class 2021-09-04 11:06:06 +08:00
Domenic Horner
9c32adfb55 Update pushbullet down body
Co-authored-by: Adam Stachowicz <saibamenppl@gmail.com>
2021-09-04 18:26:44 +08:00
Domenic Horner
d346afd33b Update pushbullet up body
Co-authored-by: Adam Stachowicz <saibamenppl@gmail.com>
2021-09-04 18:26:38 +08:00
Domenic Horner
3bf380c684 Update lunasea "up" body content
Co-authored-by: Adam Stachowicz <saibamenppl@gmail.com>
2021-09-04 18:26:10 +08:00
Domenic Horner
dca5c59982 Update lunasea body content
Co-authored-by: Adam Stachowicz <saibamenppl@gmail.com>
2021-09-04 18:24:43 +08:00
Ponkhy
8f9a973ede Merge branch 'master' into import-export 2021-09-04 01:26:48 +02:00
Ponkhy
b98ec0c3d4 Added i18n keys if necessary for some languages 2021-09-04 01:26:06 +02:00
Louis Lam
2082c3f68a Merge pull request #313 from xoniq/lang-nl
Add Dutch/Nederlands language (nl_NL)
2021-09-04 00:05:05 +08:00
Louis Lam
ea9f434553 Merge pull request #328 from Ponkhy/german-language
Small german language adjustments
2021-09-03 23:06:32 +08:00
Louis Lam
fa1216c198 Merge pull request #329 from dhfhfk/New-string
Add missing strings for Korean
2021-09-03 23:05:48 +08:00
dhfhfk
6d8aa20fc6 Add missing string for Korean
confirmDisableAuth, new string
2021-09-03 23:18:18 +09:00
Ponkhy
5430da955a Small german language adjustments 2021-09-03 15:54:14 +02:00
J Posthuma
e6e2b0ebf8 Update nl_NL.js 2021-09-03 13:42:14 +02:00
J Posthuma
445dc486be Merge branch 'master' into lang-nl 2021-09-03 13:41:10 +02:00
Louis Lam
d2151737c1 Update README.md 2021-09-03 17:09:11 +08:00
Louis Lam
78fc6de542 Merge pull request #325 from Saibamen/patch-1
Update `README.md` - Open Collective URL
2021-09-03 15:49:23 +08:00
Adam Stachowicz
c15e6631ae Update README.md
Open Collective URL
2021-09-03 09:16:54 +02:00
LouisLam
ebf362754c Merge branch 'master' into clear-monitor-data 2021-09-03 14:56:43 +08:00
Louis Lam
f84135ca67 Merge pull request #322 from chakflying/patch-6
Fix: Fix Notification "Test" btn styling
2021-09-03 14:34:39 +08:00
LouisLam
80eca886ce Merge branch 'add-serbian-latin'
# Conflicts:
#	src/main.js
#	src/pages/Settings.vue
2021-09-03 14:32:12 +08:00
Louis Lam
274a9050c0 Merge pull request #321 from dusansimic/add-serbian-cyrillic
Add Serbian Cyrillic locale
2021-09-03 14:00:43 +08:00
Louis Lam
1f4ad938b1 Merge pull request #323 from xinac721/master
Update Chinese Translation(更新简体中文语言翻译)
2021-09-03 13:55:30 +08:00
新逸Cary
312aedf391 Update Chinese Translation(更新简体中文语言翻译) 2021-09-03 13:33:26 +08:00
Louis Lam
4f28bfbde3 Merge pull request #309 from Saibamen/updateDependencies
Update dependencies
2021-09-03 12:56:55 +08:00
Louis Lam
ee76ab4ec2 Merge pull request #317 from AtaxyaNetwork/master
change translation FR
2021-09-03 12:56:11 +08:00
Nelson Chan
18616ee590 Fix: Fix Notification "Test" btn styling 2021-09-03 12:05:49 +08:00
Dušan Simić
19a4d570ec Fix word capitalization 2021-09-03 02:24:31 +02:00
Dušan Simić
79fda8f442 Add Serbian Latin locale 2021-09-03 02:20:01 +02:00
Dušan Simić
53a14cf4f5 Add Serbian Cyrilic locale 2021-09-03 02:06:26 +02:00
cecile
2241d8817f change translation FR 2021-09-02 23:41:47 +02:00
Louis Lam
3831dfe0b9 Update README.md 2021-09-03 02:55:36 +08:00
Louis Lam
fdde862549 Merge pull request #316 from aictur/feature/es-ES
Add spanish language and update Readme
2021-09-02 23:00:36 +08:00
LouisLam
e31be8caf5 demo mode 2021-09-02 22:52:20 +08:00
LouisLam
60f2f08cea add demo db 2021-09-02 22:39:04 +08:00
LouisLam
b1647a310e add demo db 2021-09-02 22:37:51 +08:00
Victor M. Vicente Cuevas
23f1a73fc8 Add language to src/main and update README 2021-09-02 16:31:45 +02:00
Victor M. Vicente Cuevas
d2bf2a551d Add spanish language and update Readme 2021-09-02 16:23:07 +02:00
Ponkhy
7d70c4d8cd Code optimizations 2021-09-02 16:13:31 +02:00
Domenic Horner
532ad3044c Add space to pushbullet and lunasea notifications
Start changes regarding standardization of notification messages
2021-09-02 21:55:25 +08:00
LouisLam
f23ecef636 add missing cert parameters 2021-09-02 21:16:04 +08:00
LouisLam
51cf2ff6f9 add missing cert parameters 2021-09-02 21:13:59 +08:00
LouisLam
b30b1d3a52 create data dir before copy 2021-09-02 21:11:20 +08:00
LouisLam
582e14098d create data dir before copy 2021-09-02 21:10:18 +08:00
LouisLam
6e3e2fc85c fix db path 2021-09-02 21:08:00 +08:00
LouisLam
b604807cfe create data dir if not exists 2021-09-02 20:42:55 +08:00
LouisLam
3ee13bddd1 dash style for args 2021-09-02 20:36:52 +08:00
LouisLam
c74986647e allow changing data dir 2021-09-02 20:27:18 +08:00
LouisLam
dac410c850 Merge remote-tracking branch 'origin/master' 2021-09-02 20:20:47 +08:00
LouisLam
b88b357b55 add support for https 2021-09-02 20:18:27 +08:00
Louis Lam
9a5cd5172b Merge pull request #314 from xinac721/master
Chinese Translation (from zh-CN.js)
2021-09-02 20:14:05 +08:00
新逸Cary
941788db49 Chinese Translation (from zh-CN.js) 2021-09-02 18:33:09 +08:00
Jelle Posthuma
99725aabe7 Add Dutch/Nederlands language (nl_NL) 2021-09-02 11:10:54 +02:00
Ponkhy
2dd392e609 Added uptime kuma version to backup file 2021-09-02 10:15:25 +02:00
Louis Lam
9d41da4aa2 Merge pull request #311 from xinac721/master
Chinese Translation (from zh-CN.js)
2021-09-02 15:59:06 +08:00
新逸Cary
a0f372e946 Chinese Translation (from zh-CN.js) 2021-09-02 15:34:46 +08:00
新逸Cary
877ad1438f Merge branch 'louislam:master' into master 2021-09-02 15:25:09 +08:00
LouisLam
9116654a33 update language files and run eslint on these 2021-09-02 15:21:46 +08:00
新逸Cary
dbc70bc5ed Merge pull request #1 from louislam/master
update
2021-09-02 14:33:34 +08:00
Louis Lam
a29b65e801 Merge pull request #308 from DX37/translation-ru
Add Russian translation
2021-09-02 12:56:12 +08:00
Louis Lam
bd9568ce5b Merge pull request #310 from Saibamen/more_i18n
Make `Resp. Time (ms)` and `N/A` i18n
2021-09-02 12:55:11 +08:00
Ponkhy
c13cc62d3d Update server/server.js
Co-authored-by: Adam Stachowicz <saibamenppl@gmail.com>
2021-09-01 21:32:33 +02:00
Adam Stachowicz
7a109689d9 Make Resp. Time (ms) and N/A i18n 2021-09-01 21:17:50 +02:00
Adam Stachowicz
e7929f461d Update dependencies 2021-09-01 20:53:12 +02:00
DX37
a2cf7f394e Add Russian translation 2021-09-02 01:37:01 +07:00
Louis Lam
e1f378ee6c Update README.md 2021-09-02 02:11:44 +08:00
LouisLam
eeb00a5511 fix data type 2021-09-02 01:56:02 +08:00
LouisLam
8e1e4b9204 override file-selector-button hover color 2021-09-02 01:51:51 +08:00
LouisLam
66f9ad5754 Merge branch 'master' into import-export 2021-09-02 01:35:42 +08:00
Louis Lam
129dc3324b Merge pull request #307 from dhfhfk/master
Added Korean Language
2021-09-02 01:17:54 +08:00
Louis Lam
268d2e2353 Merge branch 'master' into master 2021-09-02 01:17:48 +08:00
Louis Lam
7d2cf0ee57 Merge pull request #304 from LeviSnoot/master
Added Swedish Language
2021-09-02 01:17:10 +08:00
오로라
2d408732db Update main.js
Prevent conflict with #304 (Added Swedish Language)
2021-09-02 01:01:18 +09:00
dhfhfk
0cc5053f14 Update ko-KR.js 2021-09-02 00:35:50 +09:00
dhfhfk
1c4e5b79be Create ko-KR.js 2021-09-02 00:30:08 +09:00
dhfhfk
4aae402b36 Update main.js 2021-09-02 00:29:01 +09:00
Ponkhy
b604910bbb Merge branch 'master' into clear-monitor-data 2021-09-01 17:17:40 +02:00
Ponkhy
2f6c5963c5 Added import and export function 2021-09-01 17:09:32 +02:00
Levi
dce2ba8f9f Update sv-SE.js 2021-09-01 16:41:51 +02:00
Levi
ed5c75282c Update main.js 2021-09-01 16:38:05 +02:00
Levi
00ac560bd6 Create sv-SE.js 2021-09-01 16:30:08 +02:00
Louis Lam
8ea4dec5a0 Merge pull request #303 from Saibamen/patch-1
Update translation docs
2021-09-01 22:08:44 +08:00
Adam Stachowicz
3006d13aee Update translation docs 2021-09-01 15:37:56 +02:00
LouisLam
c7e6cb9f37 update language files 2021-09-01 20:10:54 +08:00
LouisLam
9b82bb248e Merge branch 'master' into kometchtech_patch-1
# Conflicts:
#	src/main.js
2021-09-01 19:46:44 +08:00
Louis Lam
e55cf65f28 Merge pull request #291 from Ponkhy/master
Added translation to Setup.vue
2021-09-01 19:45:20 +08:00
Louis Lam
f24002838c Merge branch 'master' into master 2021-09-01 19:45:06 +08:00
Louis Lam
b4fd4f7d90 Merge pull request #301 from Lrss/master
Added Danish language
2021-09-01 19:44:01 +08:00
kometchtech
3a3f1762ac Update ja.js
* Perform translation for added keys.
2021-09-01 19:04:35 +09:00
Lars Sørensen
6376d4eb92 Translated to Danish 2021-09-01 11:51:22 +02:00
LouisLam
e37cf9b1a7 add missing and add Japanese to the list 2021-09-01 17:32:41 +08:00
LouisLam
3b0191210b Merge branch 'master' into kometchtech_patch-1 2021-09-01 17:28:49 +08:00
LouisLam
923d325b44 add language file preparation script 2021-09-01 17:10:12 +08:00
LouisLam
ad38e61c26 add language file preparation script 2021-09-01 17:08:27 +08:00
LouisLam
22095c09b1 add translation guide 2021-09-01 15:49:35 +08:00
LouisLam
a5e62141d5 update to 1.5.2 2021-09-01 15:04:31 +08:00
LouisLam
e4b76717be revert back to node-sqlite3 2021-09-01 15:02:04 +08:00
LouisLam
bc70ecfba7 fix install script 2021-09-01 14:56:19 +08:00
LouisLam
f1238ab762 update to 1.5.1 2021-09-01 14:41:06 +08:00
LouisLam
cd1a3a2fb9 revert back to node-sqlite3, as better-sqlite3 causes a lot of installation problems 2021-09-01 14:33:00 +08:00
kometchtech
25db162721 Create ja.js
* Created a Japanese language file.
2021-09-01 13:23:44 +09:00
Ponkhy
7b92166d18 Added clear all db statistics function 2021-09-01 00:36:24 +02:00
Ponkhy
1341d220ed Merge branch 'louislam:master' into clear-monitor-data 2021-08-31 23:22:45 +02:00
Ponkhy
14b9afb400 Added translation to Setup.vue 2021-08-29 23:07:58 +02:00
Ponkhy
1952e34110 Added the possibility to clear monitor data 2021-08-29 18:47:01 +02:00
77 changed files with 5749 additions and 2021 deletions

View File

@@ -20,6 +20,11 @@ yarn.lock
app.json
CODE_OF_CONDUCT.md
CONTRIBUTING.md
CNAME
install.sh
SECURITY.md
tsconfig.json
### .gitignore content (commented rules are duplicated)

View File

@@ -17,7 +17,8 @@ module.exports = {
},
rules: {
"camelcase": ["warn", {
"properties": "never"
"properties": "never",
"ignoreImports": true
}],
// override/add rules settings here, such as:
// 'vue/no-unused-vars': 'error'

View File

@@ -10,12 +10,24 @@ It is a self-hosted monitoring tool like "Uptime Robot".
<img src="https://louislam.net/uptimekuma/1.jpg" width="512" alt="" />
## 🥔 Live Demo
Try it!
https://demo.uptime.kuma.pet
It is a 5 minutes live demo, all data will be deleted after that. The server is located at Tokyo, if you live far away from here, it may affact your experience. I suggest that you should install to try it.
VPS is sponsored by Uptime Kuma sponsors on [Open Collective](https://opencollective.com/uptime-kuma)! Thank you so much!
## ⭐ Features
* Monitoring uptime for HTTP(s) / TCP / Ping / DNS Record.
* Fancy, Reactive, Fast UI/UX.
* Notifications via Telegram, Discord, Gotify, Slack, Pushover, Email (SMTP), and [70+ notification services, click here for the full list](https://github.com/louislam/uptime-kuma/issues/284).
* 20 seconds interval.
* [Multi Languages](https://github.com/louislam/uptime-kuma/tree/master/src/languages)
## 🔧 How to Install
@@ -66,6 +78,10 @@ I will mark requests/issues to the next milestone.
https://github.com/louislam/uptime-kuma/milestones
Project Plan:
https://github.com/louislam/uptime-kuma/projects/1
## 🖼 More Screenshots
Dark Mode:
@@ -91,10 +107,23 @@ Telegram Notification Sample:
If you love this project, please consider giving me a ⭐.
## 🗣️ Discussion
You can also discuss or ask for help in [Issues](https://github.com/louislam/uptime-kuma/issues).
Alternatively, you can discuss in my original post on reddit: https://www.reddit.com/r/selfhosted/comments/oi7dc7/uptime_kuma_a_fancy_selfhosted_monitoring_tool_an/
I think the real "Discussion" tab is hard to use, as it is reddit-like flow, I always missed new comments.
## Contribute
If you want to report a bug or request a new feature. Free feel to open a new issue.
If you want to report a bug or request a new feature. Free feel to open a [new issue](https://github.com/louislam/uptime-kuma/issues).
If you want to translate Uptime Kuma into your langauge, please read: https://github.com/louislam/uptime-kuma/tree/master/src/languages
If you want to modify Uptime Kuma, this guideline may be useful for you: https://github.com/louislam/uptime-kuma/blob/master/CONTRIBUTING.md
English proofreading is needed too because my grammar is not that great sadly. Feel free to correct my grammar in this readme, source code, or wiki.

BIN
db/demo_kuma.db Normal file

Binary file not shown.

View File

@@ -0,0 +1,10 @@
-- You should not modify if this have pushed to Github, unless it does serious wrong with the db.
BEGIN TRANSACTION;
-- For sendHeartbeatList
CREATE INDEX monitor_time_index ON heartbeat (monitor_id, time);
-- For sendImportantHeartbeatList
CREATE INDEX monitor_important_time_index ON heartbeat (monitor_id, important,time);
COMMIT;

View File

@@ -0,0 +1,22 @@
-- You should not modify if this have pushed to Github, unless it does serious wrong with the db.
BEGIN TRANSACTION;
-- Generated by Intellij IDEA
create table setting_dg_tmp
(
id INTEGER
primary key autoincrement,
key VARCHAR(200) not null
unique,
value TEXT,
type VARCHAR(20)
);
insert into setting_dg_tmp(id, key, value, type) select id, key, value, type from setting;
drop table setting;
alter table setting_dg_tmp rename to setting;
COMMIT;

7
db/patch9.sql Normal file
View File

@@ -0,0 +1,7 @@
-- You should not modify if this have pushed to Github, unless it does serious wrong with the db.
BEGIN TRANSACTION;
ALTER TABLE notification
ADD is_default BOOLEAN default 0 NOT NULL;
COMMIT;

View File

@@ -1,18 +1,37 @@
# DON'T UPDATE TO alpine3.13, 1.14, see #41.
FROM node:14-alpine3.12 AS release
# DON'T UPDATE TO node:14-bullseye-slim, see #372.
# If the image changed, the second stage image should be changed too
FROM node:14-buster-slim AS build
WORKDIR /app
# Install apprise
RUN apk add --no-cache python3 py3-cryptography py3-pip py3-six py3-yaml py3-click py3-markdown py3-requests py3-requests-oauthlib
RUN pip3 --no-cache-dir install apprise && \
rm -rf /root/.cache
# split the sqlite install here, so that it can caches the arm prebuilt
# do not modify it, since we don't want to re-compile the arm prebuilt again
RUN apt update && \
apt --yes install python3 python3-pip python3-dev git g++ make && \
ln -s /usr/bin/python3 /usr/bin/python && \
npm install mapbox/node-sqlite3#593c9d --build-from-source
COPY . .
RUN npm install --legacy-peer-deps && npm run build && npm prune
RUN npm install --legacy-peer-deps && npm run build && npm prune --production
FROM node:14-buster-slim AS release
WORKDIR /app
# Install Apprise,
# add sqlite3 cli for debugging in the future
# iputils-ping for ping
RUN apt update && \
apt --yes install python3 python3-pip python3-cryptography python3-six python3-yaml python3-click python3-markdown python3-requests python3-requests-oauthlib \
sqlite3 \
iputils-ping && \
pip3 --no-cache-dir install apprise && \
rm -rf /var/lib/apt/lists/*
# Copy app files from build layer
COPY --from=build /app /app
EXPOSE 3001
VOLUME ["/app/data"]
HEALTHCHECK --interval=60s --timeout=30s --start-period=300s CMD node extra/healthcheck.js
HEALTHCHECK --interval=60s --timeout=30s --start-period=180s --retries=5 CMD node extra/healthcheck.js
CMD ["node", "server/server.js"]
FROM release AS nightly

33
dockerfile-alpine Normal file
View File

@@ -0,0 +1,33 @@
# DON'T UPDATE TO alpine3.13, 1.14, see #41.
FROM node:14-alpine3.12 AS build
WORKDIR /app
# split the sqlite install here, so that it can caches the arm prebuilt
RUN apk add --no-cache --virtual .build-deps make g++ python3 python3-dev git && \
ln -s /usr/bin/python3 /usr/bin/python && \
npm install mapbox/node-sqlite3#593c9d && \
apk del .build-deps && \
rm -f /usr/bin/python
COPY . .
RUN npm install --legacy-peer-deps && npm run build && npm prune --production
FROM node:14-alpine3.12 AS release
WORKDIR /app
# Install apprise
RUN apk add --no-cache python3 py3-cryptography py3-pip py3-six py3-yaml py3-click py3-markdown py3-requests py3-requests-oauthlib && \
pip3 --no-cache-dir install apprise && \
rm -rf /root/.cache
# Copy app files from build layer
COPY --from=build /app /app
EXPOSE 3001
VOLUME ["/app/data"]
HEALTHCHECK --interval=60s --timeout=30s --start-period=180s --retries=5 CMD node extra/healthcheck.js
CMD ["node", "server/server.js"]
FROM release AS nightly
RUN npm run mark-as-nightly

View File

@@ -1,19 +1,34 @@
let http = require("http");
/*
* This script should be run after a period of time (180s), because the server may need some time to prepare.
*/
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
let client;
if (process.env.SSL_KEY && process.env.SSL_CERT) {
client = require("https");
} else {
client = require("http");
}
let options = {
host: "localhost",
port: "3001",
timeout: 2000,
host: process.env.HOST || "127.0.0.1",
port: parseInt(process.env.PORT) || 3001,
timeout: 28 * 1000,
};
let request = http.request(options, (res) => {
console.log(`STATUS: ${res.statusCode}`);
if (res.statusCode == 200) {
let request = client.request(options, (res) => {
console.log(`Health Check OK [Res Code: ${res.statusCode}]`);
if (res.statusCode === 200) {
process.exit(0);
} else {
process.exit(1);
}
});
request.on("error", function (err) {
console.log("ERROR");
console.error("Health Check ERROR");
process.exit(1);
});
request.end();

View File

@@ -212,8 +212,8 @@ if (type == "local") {
bash("check=$(docker info)");
bash("if [[ \"$check\" == *\"Is the docker daemon running\"* ]]; then
echo \"Error: docker is not running\"
exit 1
\"echo\" \"Error: docker is not running\"
\"exit\" \"1\"
fi");
if ("$3" != "") {

View File

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

View File

@@ -0,0 +1,78 @@
// Need to use es6 to read language files
import fs from "fs";
import path from "path";
import util from "util";
// https://stackoverflow.com/questions/13786160/copy-folder-recursively-in-node-js
/**
* Look ma, it's cp -R.
* @param {string} src The path to the thing to copy.
* @param {string} dest The path to the new copy.
*/
const copyRecursiveSync = function (src, dest) {
let exists = fs.existsSync(src);
let stats = exists && fs.statSync(src);
let isDirectory = exists && stats.isDirectory();
if (isDirectory) {
fs.mkdirSync(dest);
fs.readdirSync(src).forEach(function (childItemName) {
copyRecursiveSync(path.join(src, childItemName),
path.join(dest, childItemName));
});
} else {
fs.copyFileSync(src, dest);
}
};
console.log(process.argv)
const baseLangCode = process.argv[2] || "zh-HK";
console.log("Base Lang: " + baseLangCode);
fs.rmdirSync("./languages", { recursive: true });
copyRecursiveSync("../../src/languages", "./languages");
const en = (await import("./languages/en.js")).default;
const baseLang = (await import(`./languages/${baseLangCode}.js`)).default;
const files = fs.readdirSync("./languages");
console.log(files);
for (const file of files) {
if (file.endsWith(".js")) {
console.log("Processing " + file);
const lang = await import("./languages/" + file);
let obj;
if (lang.default) {
console.log("is js module");
obj = lang.default;
} else {
console.log("empty file");
obj = {
languageName: "<Your Language name in your language (not in English)>"
};
}
// En first
for (const key in en) {
if (! obj[key]) {
obj[key] = en[key];
}
}
// Base second
for (const key in baseLang) {
if (! obj[key]) {
obj[key] = key;
}
}
const code = "export default " + util.inspect(obj, {
depth: null,
});
fs.writeFileSync(`../../src/languages/${file}`, code);
}
}
fs.rmdirSync("./languages", { recursive: true });
console.log("Done, fix the format by eslint now");

View File

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

View File

@@ -23,6 +23,8 @@ if (! exists) {
pkg.version = newVersion;
pkg.scripts.setup = pkg.scripts.setup.replaceAll(oldVersion, newVersion);
pkg.scripts["build-docker"] = pkg.scripts["build-docker"].replaceAll(oldVersion, newVersion);
pkg.scripts["build-docker-alpine"] = pkg.scripts["build-docker-alpine"].replaceAll(oldVersion, newVersion);
pkg.scripts["build-docker-debian"] = pkg.scripts["build-docker-debian"].replaceAll(oldVersion, newVersion);
fs.writeFileSync("package.json", JSON.stringify(pkg, null, 4) + "\n");
commit(newVersion);

View File

@@ -176,8 +176,8 @@ else
fi
check=$(docker info)
if [[ "$check" == *"Is the docker daemon running"* ]]; then
echo "Error: docker is not running"
exit 1
"echo" "Error: docker is not running"
"exit" "1"
fi
if [ "$3" != "" ]; then
port="$3"

2359
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "uptime-kuma",
"version": "1.5.0",
"version": "1.6.2",
"license": "MIT",
"repository": {
"type": "git",
@@ -16,14 +16,14 @@
"dev": "vite --host",
"start": "npm run start-server",
"start-server": "node server/server.js",
"start-demo-server": "set NODE_ENV=demo && node server/server.js",
"update": "",
"build": "vite build",
"vite-preview-dist": "vite preview --host",
"build-docker": "docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma -t louislam/uptime-kuma:1 -t louislam/uptime-kuma:1.5.0 --target release . --push",
"build-docker": "npm run build-docker-debian && npm run build-docker-alpine",
"build-docker-alpine": "docker buildx build -f dockerfile-alpine --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:alpine -t louislam/uptime-kuma:1-alpine -t louislam/uptime-kuma:1.6.2-alpine --target release . --push",
"build-docker-debian": "docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma -t louislam/uptime-kuma:1 -t louislam/uptime-kuma:1.6.2 -t louislam/uptime-kuma:debian -t louislam/uptime-kuma:1-debian -t louislam/uptime-kuma:1.6.2-debian --target release . --push",
"build-docker-nightly": "docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:nightly --target nightly . --push",
"build-docker-nightly-amd64": "docker buildx build --platform linux/amd64 -t louislam/uptime-kuma:nightly-amd64 --target nightly . --push --progress plain",
"setup": "git checkout 1.5.0 && npm install --legacy-peer-deps && npm run build && npm prune",
"setup": "git checkout 1.6.2 && npm install --legacy-peer-deps && node node_modules/esbuild/install.js && npm run build && npm prune",
"update-version": "node extra/update-version.js",
"mark-as-nightly": "node extra/mark-as-nightly.js",
"reset-password": "node extra/reset-password.js",
@@ -32,15 +32,14 @@
"test-install-script-alpine3": "npm run compile-install-script && docker build --progress plain -f test/test_install_script/alpine3.dockerfile .",
"test-install-script-ubuntu": "npm run compile-install-script && docker build --progress plain -f test/test_install_script/ubuntu.dockerfile .",
"test-install-script-ubuntu1604": "npm run compile-install-script && docker build --progress plain -f test/test_install_script/ubuntu1604.dockerfile .",
"test-install-script-debian": "npm run compile-install-script && docker build --progress plain -f test/test_install_script/debian.dockerfile .",
"simple-dns-server": "node extra/simple-dns-server.js"
"simple-dns-server": "node extra/simple-dns-server.js",
"update-language-files": "cd extra/update-language-files && node index.js %npm_config_base_lang% && eslint ../../src/languages/**.js --fix"
},
"dependencies": {
"@fortawesome/fontawesome-svg-core": "^1.2.36",
"@fortawesome/free-regular-svg-icons": "^5.15.4",
"@fortawesome/free-solid-svg-icons": "^5.15.4",
"@fortawesome/vue-fontawesome": "^3.0.0-4",
"@louislam/better-sqlite3-with-prebuilds": "^7.4.3",
"@popperjs/core": "^2.9.3",
"args-parser": "^1.3.0",
"axios": "^0.21.1",
@@ -54,18 +53,19 @@
"express": "^4.17.1",
"express-basic-auth": "^1.2.0",
"form-data": "^4.0.0",
"http-graceful-shutdown": "^3.1.3",
"http-graceful-shutdown": "^3.1.4",
"jsonwebtoken": "^8.5.1",
"nodemailer": "^6.6.3",
"password-hash": "^1.2.2",
"prom-client": "^13.2.0",
"prometheus-api-metrics": "^3.2.0",
"redbean-node": "0.1.2",
"socket.io": "^4.1.3",
"socket.io-client": "^4.1.3",
"socket.io": "^4.2.0",
"socket.io-client": "^4.2.0",
"sqlite3": "github:mapbox/node-sqlite3#593c9d",
"tcp-ping": "^0.1.1",
"v-pagination-3": "^0.1.6",
"vue": "^3.2.2",
"vue": "^3.2.8",
"vue-chart-3": "^0.5.7",
"vue-confirm-dialog": "^1.0.2",
"vue-i18n": "^9.1.7",
@@ -75,18 +75,18 @@
},
"devDependencies": {
"@babel/eslint-parser": "^7.15.0",
"@types/bootstrap": "^5.1.1",
"@vitejs/plugin-legacy": "^1.5.1",
"@vitejs/plugin-vue": "^1.4.0",
"@vue/compiler-sfc": "^3.2.2",
"core-js": "^3.16.1",
"@types/bootstrap": "^5.1.2",
"@vitejs/plugin-legacy": "^1.5.2",
"@vitejs/plugin-vue": "^1.6.0",
"@vue/compiler-sfc": "^3.2.6",
"core-js": "^3.17.0",
"dns2": "^2.0.1",
"eslint": "^7.32.0",
"eslint-plugin-vue": "^7.16.0",
"sass": "^1.37.5",
"eslint-plugin-vue": "^7.17.0",
"sass": "^1.38.2",
"stylelint": "^13.13.1",
"stylelint-config-standard": "^22.0.0",
"typescript": "^4.3.5",
"vite": "^2.4.4"
"typescript": "^4.4.2",
"vite": "^2.5.3"
}
}

88
server/client.js Normal file
View File

@@ -0,0 +1,88 @@
/*
* For Client Socket
*/
const { TimeLogger } = require("../src/util");
const { R } = require("redbean-node");
const { io } = require("./server");
async function sendNotificationList(socket) {
const timeLogger = new TimeLogger();
let result = [];
let list = await R.find("notification", " user_id = ? ", [
socket.userID,
]);
for (let bean of list) {
result.push(bean.export())
}
io.to(socket.userID).emit("notificationList", result)
timeLogger.print("Send Notification List");
return list;
}
/**
* Send Heartbeat History list to socket
* @param toUser True = send to all browsers with the same user id, False = send to the current browser only
* @param overwrite Overwrite client-side's heartbeat list
*/
async function sendHeartbeatList(socket, monitorID, toUser = false, overwrite = false) {
const timeLogger = new TimeLogger();
let list = await R.getAll(`
SELECT * FROM heartbeat
WHERE monitor_id = ?
ORDER BY time DESC
LIMIT 100
`, [
monitorID,
])
let result = list.reverse();
if (toUser) {
io.to(socket.userID).emit("heartbeatList", monitorID, result, overwrite);
} else {
socket.emit("heartbeatList", monitorID, result, overwrite);
}
timeLogger.print(`[Monitor: ${monitorID}] sendHeartbeatList`);
}
/**
* Important Heart beat list (aka event list)
* @param socket
* @param monitorID
* @param toUser True = send to all browsers with the same user id, False = send to the current browser only
* @param overwrite Overwrite client-side's heartbeat list
*/
async function sendImportantHeartbeatList(socket, monitorID, toUser = false, overwrite = false) {
const timeLogger = new TimeLogger();
let list = await R.find("heartbeat", `
monitor_id = ?
AND important = 1
ORDER BY time DESC
LIMIT 500
`, [
monitorID,
])
timeLogger.print(`[Monitor: ${monitorID}] sendImportantHeartbeatList`);
if (toUser) {
io.to(socket.userID).emit("importantHeartbeatList", monitorID, list, overwrite);
} else {
socket.emit("importantHeartbeatList", monitorID, list, overwrite);
}
}
module.exports = {
sendNotificationList,
sendImportantHeartbeatList,
sendHeartbeatList,
}

View File

@@ -1,21 +1,48 @@
const fs = require("fs");
const { R } = require("redbean-node");
const { setSetting, setting } = require("./util-server");
const { debug, sleep } = require("../src/util");
const dayjs = require("dayjs");
class Database {
static templatePath = "./db/kuma.db"
static path = "./data/kuma.db";
static latestVersion = 8;
static templatePath = "./db/kuma.db";
static dataDir;
static path;
/**
* @type {boolean}
*/
static patched = false;
/**
* For Backup only
*/
static backupPath = null;
/**
* Add patch filename in key
* Values:
* true: Add it regardless of order
* false: Do nothing
* { parents: []}: Need parents before add it
*/
static patchList = {
"patch-setting-value-type.sql": true,
"patch-improve-performance.sql": true,
}
/**
* The finally version should be 10 after merged tag feature
* @deprecated Use patchList for any new feature
*/
static latestVersion = 9;
static noReject = true;
static sqliteInstance = null;
static async connect() {
const acquireConnectionTimeout = 120 * 1000;
R.useBetterSQLite3 = true;
R.betterSQLite3Options.timeout = acquireConnectionTimeout;
R.setup("sqlite", {
filename: Database.path,
useNullAsDefault: true,
@@ -38,7 +65,11 @@ class Database {
// Change to WAL
await R.exec("PRAGMA journal_mode = WAL");
await R.exec("PRAGMA cache_size = -12000");
console.log("SQLite config:");
console.log(await R.getAll("PRAGMA journal_mode"));
console.log(await R.getAll("PRAGMA cache_size"));
}
static async patch() {
@@ -58,19 +89,7 @@ class Database {
} else {
console.info("Database patch is needed")
console.info("Backup the db")
const backupPath = "./data/kuma.db.bak" + version;
fs.copyFileSync(Database.path, backupPath);
const shmPath = Database.path + "-shm";
if (fs.existsSync(shmPath)) {
fs.copyFileSync(shmPath, shmPath + ".bak" + version);
}
const walPath = Database.path + "-wal";
if (fs.existsSync(walPath)) {
fs.copyFileSync(walPath, walPath + ".bak" + version);
}
this.backup(version);
// Try catch anything here, if gone wrong, restore the backup
try {
@@ -81,18 +100,92 @@ class Database {
console.info(`Patched ${sqlFile}`);
await setSetting("database_version", i);
}
console.log("Database Patched Successfully");
} catch (ex) {
await Database.close();
console.error("Patch db failed!!! Restoring the backup")
fs.copyFileSync(backupPath, Database.path);
console.error(ex)
this.restore();
console.error(ex)
console.error("Start Uptime-Kuma failed due to patch db failed")
console.error("Please submit the bug report if you still encounter the problem after restart: https://github.com/louislam/uptime-kuma/issues")
process.exit(1);
}
}
await this.patch2();
}
/**
* Call it from patch() only
* @returns {Promise<void>}
*/
static async patch2() {
console.log("Database Patch 2.0 Process");
let databasePatchedFiles = await setting("databasePatchedFiles");
if (! databasePatchedFiles) {
databasePatchedFiles = {};
}
debug("Patched files:");
debug(databasePatchedFiles);
try {
for (let sqlFilename in this.patchList) {
await this.patch2Recursion(sqlFilename, databasePatchedFiles)
}
if (this.patched) {
console.log("Database Patched Successfully");
}
} catch (ex) {
await Database.close();
this.restore();
console.error(ex)
console.error("Start Uptime-Kuma failed due to patch db failed");
console.error("Please submit the bug report if you still encounter the problem after restart: https://github.com/louislam/uptime-kuma/issues");
process.exit(1);
}
await setSetting("databasePatchedFiles", databasePatchedFiles);
}
/**
* Used it patch2() only
* @param sqlFilename
* @param databasePatchedFiles
*/
static async patch2Recursion(sqlFilename, databasePatchedFiles) {
let value = this.patchList[sqlFilename];
if (! value) {
console.log(sqlFilename + " skip");
return;
}
// Check if patched
if (! databasePatchedFiles[sqlFilename]) {
console.log(sqlFilename + " is not patched");
if (value.parents) {
console.log(sqlFilename + " need parents");
for (let parentSQLFilename of value.parents) {
await this.patch2Recursion(parentSQLFilename, databasePatchedFiles);
}
}
this.backup(dayjs().format("YYYYMMDDHHmmss"));
console.log(sqlFilename + " is patching");
this.patched = true;
await this.importSQLFile("./db/" + sqlFilename);
databasePatchedFiles[sqlFilename] = true;
console.log(sqlFilename + " is patched successfully");
} else {
console.log(sqlFilename + " is already patched, skip");
}
}
/**
@@ -124,11 +217,8 @@ class Database {
return statement !== "";
})
// Use better-sqlite3 to run, prevent "This statement does not return data. Use run() instead"
const db = await this.getBetterSQLite3Database();
for (let statement of statements) {
db.prepare(statement).run();
await R.exec(statement);
}
}
@@ -141,10 +231,96 @@ class Database {
* @returns {Promise<void>}
*/
static async close() {
if (this.sqliteInstance) {
this.sqliteInstance.close();
const listener = (reason, p) => {
Database.noReject = false;
};
process.addListener("unhandledRejection", listener);
console.log("Closing DB");
while (true) {
Database.noReject = true;
await R.close();
await sleep(2000);
if (Database.noReject) {
break;
} else {
console.log("Waiting to close the db");
}
}
console.log("SQLite closed");
process.removeListener("unhandledRejection", listener);
}
/**
* One backup one time in this process.
* Reset this.backupPath if you want to backup again
* @param version
*/
static backup(version) {
if (! this.backupPath) {
console.info("Backup the db")
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);
}
}
}
/**
*
*/
static restore() {
if (this.backupPath) {
console.error("Patch db failed!!! Restoring the backup");
const shmPath = Database.path + "-shm";
const walPath = Database.path + "-wal";
// 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) {
console.log("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 {
console.log("Nothing to restore");
}
console.log("Stopped database");
}
}

View File

@@ -309,7 +309,10 @@ class Monitor extends BeanModel {
previousBeat = bean;
this.heartbeatInterval = setTimeout(beat, this.interval * 1000);
if (! this.isStop) {
this.heartbeatInterval = setTimeout(beat, this.interval * 1000);
}
}
beat();
@@ -317,6 +320,7 @@ class Monitor extends BeanModel {
stop() {
clearTimeout(this.heartbeatInterval);
this.isStop = true;
}
/**
@@ -405,58 +409,59 @@ class Monitor extends BeanModel {
static async sendUptime(duration, io, monitorID, userID) {
const timeLogger = new TimeLogger();
let sec = duration * 3600;
const startTime = R.isoDateTime(dayjs.utc().subtract(duration, "hour"));
let heartbeatList = await R.getAll(`
SELECT duration, time, status
// Handle if heartbeat duration longer than the target duration
// e.g. If the last beat's duration is bigger that the 24hrs window, it will use the duration between the (beat time - window margin) (THEN case in SQL)
let result = await R.getRow(`
SELECT
-- SUM all duration, also trim off the beat out of time window
SUM(
CASE
WHEN (JULIANDAY(\`time\`) - JULIANDAY(?)) * 86400 < duration
THEN (JULIANDAY(\`time\`) - JULIANDAY(?)) * 86400
ELSE duration
END
) AS total_duration,
-- SUM all uptime duration, also trim off the beat out of time window
SUM(
CASE
WHEN (status = 1)
THEN
CASE
WHEN (JULIANDAY(\`time\`) - JULIANDAY(?)) * 86400 < duration
THEN (JULIANDAY(\`time\`) - JULIANDAY(?)) * 86400
ELSE duration
END
END
) AS uptime_duration
FROM heartbeat
WHERE time > DATETIME('now', ? || ' hours')
AND monitor_id = ? `, [
-duration,
WHERE time > ?
AND monitor_id = ?
`, [
startTime, startTime, startTime, startTime, startTime,
monitorID,
]);
timeLogger.print(`[Monitor: ${monitorID}][${duration}] sendUptime`);
let downtime = 0;
let total = 0;
let uptime;
let totalDuration = result.total_duration;
let uptimeDuration = result.uptime_duration;
let uptime = 0;
// Special handle for the first heartbeat only
if (heartbeatList.length === 1) {
if (heartbeatList[0].status === 1) {
uptime = 1;
} else {
if (totalDuration > 0) {
uptime = uptimeDuration / totalDuration;
if (uptime < 0) {
uptime = 0;
}
} else {
for (let row of heartbeatList) {
let value = parseInt(row.duration)
let time = row.time
// Handle if heartbeat duration longer than the target duration
// e.g. Heartbeat duration = 28hrs, but target duration = 24hrs
if (value > sec) {
let trim = dayjs.utc().diff(dayjs(time), "second");
value = sec - trim;
if (value < 0) {
value = 0;
}
}
total += value;
if (row.status === 0 || row.status === 2) {
downtime += value;
}
}
uptime = (total - downtime) / total;
if (uptime < 0) {
uptime = 0;
// Handle new monitor with only one beat, because the beat's duration = 0
let status = parseInt(await R.getCell("SELECT `status` FROM heartbeat WHERE monitor_id = ?", [ monitorID ]));
console.log("here???" + status);
if (status === UP) {
uptime = 1;
}
}

View File

@@ -0,0 +1,26 @@
const NotificationProvider = require("./notification-provider");
const child_process = require("child_process");
class Apprise extends NotificationProvider {
name = "apprise";
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
let s = child_process.spawnSync("apprise", [ "-vv", "-b", msg, notification.appriseURL])
let output = (s.stdout) ? s.stdout.toString() : "ERROR: maybe apprise not found";
if (output) {
if (! output.includes("ERROR")) {
return "Sent Successfully";
}
throw new Error(output)
} else {
return "No output from apprise";
}
}
}
module.exports = Apprise;

View File

@@ -0,0 +1,105 @@
const NotificationProvider = require("./notification-provider");
const axios = require("axios");
const { DOWN, UP } = require("../../src/util");
class Discord extends NotificationProvider {
name = "discord";
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
let okMsg = "Sent Successfully. ";
try {
const discordDisplayName = notification.discordUsername || "Uptime Kuma";
// If heartbeatJSON is null, assume we're testing.
if (heartbeatJSON == null) {
let discordtestdata = {
username: discordDisplayName,
content: msg,
}
await axios.post(notification.discordWebhookUrl, discordtestdata)
return okMsg;
}
let url;
if (monitorJSON["type"] === "port") {
url = monitorJSON["hostname"];
if (monitorJSON["port"]) {
url += ":" + monitorJSON["port"];
}
} else {
url = monitorJSON["url"];
}
// If heartbeatJSON is not null, we go into the normal alerting loop.
if (heartbeatJSON["status"] == DOWN) {
let discorddowndata = {
username: discordDisplayName,
embeds: [{
title: "❌ Your service " + monitorJSON["name"] + " went down. ❌",
color: 16711680,
timestamp: heartbeatJSON["time"],
fields: [
{
name: "Service Name",
value: monitorJSON["name"],
},
{
name: "Service URL",
value: url,
},
{
name: "Time (UTC)",
value: heartbeatJSON["time"],
},
{
name: "Error",
value: heartbeatJSON["msg"],
},
],
}],
}
await axios.post(notification.discordWebhookUrl, discorddowndata)
return okMsg;
} else if (heartbeatJSON["status"] == UP) {
let discordupdata = {
username: discordDisplayName,
embeds: [{
title: "✅ Your service " + monitorJSON["name"] + " is up! ✅",
color: 65280,
timestamp: heartbeatJSON["time"],
fields: [
{
name: "Service Name",
value: monitorJSON["name"],
},
{
name: "Service URL",
value: url.startsWith("http") ? "[Visit Service](" + url + ")" : url,
},
{
name: "Time (UTC)",
value: heartbeatJSON["time"],
},
{
name: "Ping",
value: heartbeatJSON["ping"] + "ms",
},
],
}],
}
await axios.post(notification.discordWebhookUrl, discordupdata)
return okMsg;
}
} catch (error) {
this.throwGeneralAxiosError(error)
}
}
}
module.exports = Discord;

View File

@@ -0,0 +1,28 @@
const NotificationProvider = require("./notification-provider");
const axios = require("axios");
class Gotify extends NotificationProvider {
name = "gotify";
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
let okMsg = "Sent Successfully. ";
try {
if (notification.gotifyserverurl && notification.gotifyserverurl.endsWith("/")) {
notification.gotifyserverurl = notification.gotifyserverurl.slice(0, -1);
}
await axios.post(`${notification.gotifyserverurl}/message?token=${notification.gotifyapplicationToken}`, {
"message": msg,
"priority": notification.gotifyPriority || 8,
"title": "Uptime-Kuma",
})
return okMsg;
} catch (error) {
this.throwGeneralAxiosError(error);
}
}
}
module.exports = Gotify;

View File

@@ -0,0 +1,60 @@
const NotificationProvider = require("./notification-provider");
const axios = require("axios");
const { DOWN, UP } = require("../../src/util");
class Line extends NotificationProvider {
name = "line";
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
let okMsg = "Sent Successfully. ";
try {
let lineAPIUrl = "https://api.line.me/v2/bot/message/push";
let config = {
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer " + notification.lineChannelAccessToken
}
};
if (heartbeatJSON == null) {
let testMessage = {
"to": notification.lineUserID,
"messages": [
{
"type": "text",
"text": "Test Successful!"
}
]
}
await axios.post(lineAPIUrl, testMessage, config)
} else if (heartbeatJSON["status"] == DOWN) {
let downMessage = {
"to": notification.lineUserID,
"messages": [
{
"type": "text",
"text": "UptimeKuma Alert: [🔴 Down]\n" + "Name: " + monitorJSON["name"] + " \n" + heartbeatJSON["msg"] + "\nTime (UTC): " + heartbeatJSON["time"]
}
]
}
await axios.post(lineAPIUrl, downMessage, config)
} else if (heartbeatJSON["status"] == UP) {
let upMessage = {
"to": notification.lineUserID,
"messages": [
{
"type": "text",
"text": "UptimeKuma Alert: [✅ Up]\n" + "Name: " + monitorJSON["name"] + " \n" + heartbeatJSON["msg"] + "\nTime (UTC): " + heartbeatJSON["time"]
}
]
}
await axios.post(lineAPIUrl, upMessage, config)
}
return okMsg;
} catch (error) {
this.throwGeneralAxiosError(error)
}
}
}
module.exports = Line;

View File

@@ -0,0 +1,48 @@
const NotificationProvider = require("./notification-provider");
const axios = require("axios");
const { DOWN, UP } = require("../../src/util");
class LunaSea extends NotificationProvider {
name = "lunasea";
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
let okMsg = "Sent Successfully. ";
let lunaseadevice = "https://notify.lunasea.app/v1/custom/device/" + notification.lunaseaDevice
try {
if (heartbeatJSON == null) {
let testdata = {
"title": "Uptime Kuma Alert",
"body": "Testing Successful.",
}
await axios.post(lunaseadevice, testdata)
return okMsg;
}
if (heartbeatJSON["status"] == DOWN) {
let downdata = {
"title": "UptimeKuma Alert: " + monitorJSON["name"],
"body": "[🔴 Down] " + heartbeatJSON["msg"] + "\nTime (UTC): " + heartbeatJSON["time"],
}
await axios.post(lunaseadevice, downdata)
return okMsg;
}
if (heartbeatJSON["status"] == UP) {
let updata = {
"title": "UptimeKuma Alert: " + monitorJSON["name"],
"body": "[✅ Up] " + heartbeatJSON["msg"] + "\nTime (UTC): " + heartbeatJSON["time"],
}
await axios.post(lunaseadevice, updata)
return okMsg;
}
} catch (error) {
this.throwGeneralAxiosError(error)
}
}
}
module.exports = LunaSea;

View File

@@ -0,0 +1,123 @@
const NotificationProvider = require("./notification-provider");
const axios = require("axios");
const { DOWN, UP } = require("../../src/util");
class Mattermost extends NotificationProvider {
name = "mattermost";
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
let okMsg = "Sent Successfully. ";
try {
const mattermostUserName = notification.mattermostusername || "Uptime Kuma";
// If heartbeatJSON is null, assume we're testing.
if (heartbeatJSON == null) {
let mattermostTestData = {
username: mattermostUserName,
text: msg,
}
await axios.post(notification.mattermostWebhookUrl, mattermostTestData)
return okMsg;
}
const mattermostChannel = notification.mattermostchannel;
const mattermostIconEmoji = notification.mattermosticonemo;
const mattermostIconUrl = notification.mattermosticonurl;
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;
}
} catch (error) {
this.throwGeneralAxiosError(error);
}
}
}
module.exports = Mattermost;

View File

@@ -0,0 +1,36 @@
class NotificationProvider {
/**
* Notification Provider Name
* @type string
*/
name = undefined;
/**
* @param notification : BeanModel
* @param msg : string General Message
* @param monitorJSON : object Monitor details (For Up/Down only)
* @param heartbeatJSON : object Heartbeat details (For Up/Down only)
* @returns {Promise<string>} Return Successful Message
* Throw Error with fail msg
*/
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
throw new Error("Have to override Notification.send(...)");
}
throwGeneralAxiosError(error) {
let msg = "Error: " + error + " ";
if (error.response && error.response.data) {
if (typeof error.response.data === "string") {
msg += error.response.data;
} else {
msg += JSON.stringify(error.response.data)
}
}
throw new Error(msg)
}
}
module.exports = NotificationProvider;

View File

@@ -0,0 +1,40 @@
const NotificationProvider = require("./notification-provider");
const axios = require("axios");
class Octopush extends NotificationProvider {
name = "octopush";
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
let okMsg = "Sent Successfully. ";
try {
let config = {
headers: {
"api-key": notification.octopushAPIKey,
"api-login": notification.octopushLogin,
"cache-control": "no-cache"
}
};
let data = {
"recipients": [
{
"phone_number": notification.octopushPhoneNumber
}
],
//octopush not supporting non ascii char
"text": msg.replace(/[^\x00-\x7F]/g, ""),
"type": notification.octopushSMSType,
"purpose": "alert",
"sender": notification.octopushSenderName
};
await axios.post("https://api.octopush.com/v1/public/sms-campaign/send", data, config)
return okMsg;
} catch (error) {
this.throwGeneralAxiosError(error);
}
}
}
module.exports = Octopush;

View File

@@ -0,0 +1,50 @@
const NotificationProvider = require("./notification-provider");
const axios = require("axios");
const { DOWN, UP } = require("../../src/util");
class Pushbullet extends NotificationProvider {
name = "pushbullet";
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
let okMsg = "Sent Successfully. ";
try {
let pushbulletUrl = "https://api.pushbullet.com/v2/pushes";
let config = {
headers: {
"Access-Token": notification.pushbulletAccessToken,
"Content-Type": "application/json"
}
};
if (heartbeatJSON == null) {
let testdata = {
"type": "note",
"title": "Uptime Kuma Alert",
"body": "Testing Successful.",
}
await axios.post(pushbulletUrl, testdata, config)
} else if (heartbeatJSON["status"] == DOWN) {
let downdata = {
"type": "note",
"title": "UptimeKuma Alert: " + monitorJSON["name"],
"body": "[🔴 Down] " + heartbeatJSON["msg"] + "\nTime (UTC): " + heartbeatJSON["time"],
}
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"],
}
await axios.post(pushbulletUrl, updata, config)
}
return okMsg;
} catch (error) {
this.throwGeneralAxiosError(error)
}
}
}
module.exports = Pushbullet;

View File

@@ -0,0 +1,49 @@
const NotificationProvider = require("./notification-provider");
const axios = require("axios");
class Pushover extends NotificationProvider {
name = "pushover";
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
let okMsg = "Sent Successfully. ";
let pushoverlink = "https://api.pushover.net/1/messages.json"
try {
if (heartbeatJSON == null) {
let data = {
"message": "<b>Uptime Kuma Pushover testing successful.</b>",
"user": notification.pushoveruserkey,
"token": notification.pushoverapptoken,
"sound": notification.pushoversounds,
"priority": notification.pushoverpriority,
"title": notification.pushovertitle,
"retry": "30",
"expire": "3600",
"html": 1,
}
await axios.post(pushoverlink, data)
return okMsg;
}
let data = {
"message": "<b>Uptime Kuma Alert</b>\n\n<b>Message</b>:" + msg + "\n<b>Time (UTC)</b>:" + heartbeatJSON["time"],
"user": notification.pushoveruserkey,
"token": notification.pushoverapptoken,
"sound": notification.pushoversounds,
"priority": notification.pushoverpriority,
"title": notification.pushovertitle,
"retry": "30",
"expire": "3600",
"html": 1,
}
await axios.post(pushoverlink, data)
return okMsg;
} catch (error) {
this.throwGeneralAxiosError(error)
}
}
}
module.exports = Pushover;

View File

@@ -0,0 +1,30 @@
const NotificationProvider = require("./notification-provider");
const axios = require("axios");
class Pushy extends NotificationProvider {
name = "pushy";
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
let okMsg = "Sent Successfully. ";
try {
await axios.post(`https://api.pushy.me/push?api_key=${notification.pushyAPIKey}`, {
"to": notification.pushyToken,
"data": {
"message": "Uptime-Kuma"
},
"notification": {
"body": msg,
"badge": 1,
"sound": "ping.aiff"
}
})
return okMsg;
} catch (error) {
this.throwGeneralAxiosError(error)
}
}
}
module.exports = Pushy;

View File

@@ -0,0 +1,46 @@
const NotificationProvider = require("./notification-provider");
const axios = require("axios");
class RocketChat extends NotificationProvider {
name = "rocket.chat";
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
let okMsg = "Sent Successfully. ";
try {
if (heartbeatJSON == null) {
let data = {
"text": "Uptime Kuma Rocket.chat testing successful.",
"channel": notification.rocketchannel,
"username": notification.rocketusername,
"icon_emoji": notification.rocketiconemo,
}
await axios.post(notification.rocketwebhookURL, data)
return okMsg;
}
const time = heartbeatJSON["time"];
let data = {
"text": "Uptime Kuma Alert",
"channel": notification.rocketchannel,
"username": notification.rocketusername,
"icon_emoji": notification.rocketiconemo,
"attachments": [
{
"title": "Uptime Kuma Alert *Time (UTC)*\n" + time,
"title_link": notification.rocketbutton,
"text": "*Message*\n" + msg,
"color": "#32cd32"
}
]
}
await axios.post(notification.rocketwebhookURL, data)
return okMsg;
} catch (error) {
this.throwGeneralAxiosError(error)
}
}
}
module.exports = RocketChat;

View File

@@ -0,0 +1,27 @@
const NotificationProvider = require("./notification-provider");
const axios = require("axios");
class Signal extends NotificationProvider {
name = "signal";
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
let okMsg = "Sent Successfully. ";
try {
let data = {
"message": msg,
"number": notification.signalNumber,
"recipients": notification.signalRecipients.replace(/\s/g, "").split(","),
};
let config = {};
await axios.post(notification.signalURL, data, config)
return okMsg;
} catch (error) {
this.throwGeneralAxiosError(error)
}
}
}
module.exports = Signal;

View File

@@ -0,0 +1,70 @@
const NotificationProvider = require("./notification-provider");
const axios = require("axios");
class Slack extends NotificationProvider {
name = "slack";
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
let okMsg = "Sent Successfully. ";
try {
if (heartbeatJSON == null) {
let data = {
"text": "Uptime Kuma Slack testing successful.",
"channel": notification.slackchannel,
"username": notification.slackusername,
"icon_emoji": notification.slackiconemo,
}
await axios.post(notification.slackwebhookURL, data)
return okMsg;
}
const time = heartbeatJSON["time"];
let data = {
"text": "Uptime Kuma Alert",
"channel": notification.slackchannel,
"username": notification.slackusername,
"icon_emoji": notification.slackiconemo,
"blocks": [{
"type": "header",
"text": {
"type": "plain_text",
"text": "Uptime Kuma Alert",
},
},
{
"type": "section",
"fields": [{
"type": "mrkdwn",
"text": "*Message*\n" + msg,
},
{
"type": "mrkdwn",
"text": "*Time (UTC)*\n" + time,
}],
},
{
"type": "actions",
"elements": [
{
"type": "button",
"text": {
"type": "plain_text",
"text": "Visit Uptime Kuma",
},
"value": "Uptime-Kuma",
"url": notification.slackbutton || "https://github.com/louislam/uptime-kuma",
},
],
}],
}
await axios.post(notification.slackwebhookURL, data)
return okMsg;
} catch (error) {
this.throwGeneralAxiosError(error)
}
}
}
module.exports = Slack;

View File

@@ -0,0 +1,48 @@
const nodemailer = require("nodemailer");
const NotificationProvider = require("./notification-provider");
class SMTP extends NotificationProvider {
name = "smtp";
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
const config = {
host: notification.smtpHost,
port: notification.smtpPort,
secure: notification.smtpSecure,
};
// Should fix the issue in https://github.com/louislam/uptime-kuma/issues/26#issuecomment-896373904
if (notification.smtpUsername || notification.smtpPassword) {
config.auth = {
user: notification.smtpUsername,
pass: notification.smtpPassword,
};
}
let transporter = nodemailer.createTransport(config);
let bodyTextContent = msg;
if (heartbeatJSON) {
bodyTextContent = `${msg}\nTime (UTC): ${heartbeatJSON["time"]}`;
}
// send mail with defined transport object
await transporter.sendMail({
from: notification.smtpFrom,
cc: notification.smtpCC,
bcc: notification.smtpBCC,
to: notification.smtpTo,
subject: msg,
text: bodyTextContent,
tls: {
rejectUnauthorized: notification.smtpIgnoreTLSError || false,
},
});
return "Sent Successfully.";
}
}
module.exports = SMTP;

View File

@@ -0,0 +1,27 @@
const NotificationProvider = require("./notification-provider");
const axios = require("axios");
class Telegram extends NotificationProvider {
name = "telegram";
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
let okMsg = "Sent Successfully. ";
try {
await axios.get(`https://api.telegram.org/bot${notification.telegramBotToken}/sendMessage`, {
params: {
chat_id: notification.telegramChatID,
text: msg,
},
})
return okMsg;
} catch (error) {
let msg = (error.response.data.description) ? error.response.data.description : "Error without description"
throw new Error(msg)
}
}
}
module.exports = Telegram;

View File

@@ -0,0 +1,44 @@
const NotificationProvider = require("./notification-provider");
const axios = require("axios");
const FormData = require("form-data");
class Webhook extends NotificationProvider {
name = "webhook";
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
let okMsg = "Sent Successfully. ";
try {
let data = {
heartbeat: heartbeatJSON,
monitor: monitorJSON,
msg,
};
let finalData;
let config = {};
if (notification.webhookContentType === "form-data") {
finalData = new FormData();
finalData.append("data", JSON.stringify(data));
config = {
headers: finalData.getHeaders(),
}
} else {
finalData = data;
}
await axios.post(notification.webhookURL, finalData, config)
return okMsg;
} catch (error) {
this.throwGeneralAxiosError(error)
}
}
}
module.exports = Webhook;

View File

@@ -1,600 +1,75 @@
const axios = require("axios");
const { R } = require("redbean-node");
const FormData = require("form-data");
const nodemailer = require("nodemailer");
const child_process = require("child_process");
const Apprise = require("./notification-providers/apprise");
const Discord = require("./notification-providers/discord");
const Gotify = require("./notification-providers/gotify");
const Line = require("./notification-providers/line");
const LunaSea = require("./notification-providers/lunasea");
const Mattermost = require("./notification-providers/mattermost");
const Octopush = require("./notification-providers/octopush");
const Pushbullet = require("./notification-providers/pushbullet");
const Pushover = require("./notification-providers/pushover");
const Pushy = require("./notification-providers/pushy");
const RocketChat = require("./notification-providers/rocket-chat");
const Signal = require("./notification-providers/signal");
const Slack = require("./notification-providers/slack");
const SMTP = require("./notification-providers/smtp");
const Telegram = require("./notification-providers/telegram");
const Webhook = require("./notification-providers/webhook");
class Notification {
providerList = {};
static init() {
console.log("Prepare Notification Providers");
this.providerList = {};
const list = [
new Apprise(),
new Discord(),
new Gotify(),
new Line(),
new LunaSea(),
new Mattermost(),
new Octopush(),
new Pushbullet(),
new Pushover(),
new Pushy(),
new RocketChat(),
new Signal(),
new Slack(),
new SMTP(),
new Telegram(),
new Webhook(),
];
for (let item of list) {
if (! item.name) {
throw new Error("Notification provider without name");
}
if (this.providerList[item.name]) {
throw new Error("Duplicate notification provider name");
}
this.providerList[item.name] = item;
}
}
/**
*
* @param notification
* @param msg
* @param monitorJSON
* @param heartbeatJSON
* @param notification : BeanModel
* @param msg : string General Message
* @param monitorJSON : object Monitor details (For Up/Down only)
* @param heartbeatJSON : object Heartbeat details (For Up/Down only)
* @returns {Promise<string>} Successful msg
* Throw Error with fail msg
*/
static async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
let okMsg = "Sent Successfully. ";
if (notification.type === "telegram") {
try {
await axios.get(`https://api.telegram.org/bot${notification.telegramBotToken}/sendMessage`, {
params: {
chat_id: notification.telegramChatID,
text: msg,
},
})
return okMsg;
} catch (error) {
let msg = (error.response.data.description) ? error.response.data.description : "Error without description"
throw new Error(msg)
}
} else if (notification.type === "gotify") {
try {
if (notification.gotifyserverurl && notification.gotifyserverurl.endsWith("/")) {
notification.gotifyserverurl = notification.gotifyserverurl.slice(0, -1);
}
await axios.post(`${notification.gotifyserverurl}/message?token=${notification.gotifyapplicationToken}`, {
"message": msg,
"priority": notification.gotifyPriority || 8,
"title": "Uptime-Kuma",
})
return okMsg;
} catch (error) {
throwGeneralAxiosError(error)
}
} else if (notification.type === "webhook") {
try {
let data = {
heartbeat: heartbeatJSON,
monitor: monitorJSON,
msg,
};
let finalData;
let config = {};
if (notification.webhookContentType === "form-data") {
finalData = new FormData();
finalData.append("data", JSON.stringify(data));
config = {
headers: finalData.getHeaders(),
}
} else {
finalData = data;
}
await axios.post(notification.webhookURL, finalData, config)
return okMsg;
} catch (error) {
throwGeneralAxiosError(error)
}
} else if (notification.type === "smtp") {
return await Notification.smtp(notification, msg)
} else if (notification.type === "discord") {
try {
const discordDisplayName = notification.discordUsername || "Uptime Kuma";
// If heartbeatJSON is null, assume we're testing.
if (heartbeatJSON == null) {
let discordtestdata = {
username: discordDisplayName,
content: msg,
}
await axios.post(notification.discordWebhookUrl, discordtestdata)
return okMsg;
}
let url;
if (monitorJSON["type"] === "port") {
url = monitorJSON["hostname"];
if (monitorJSON["port"]) {
url += ":" + monitorJSON["port"];
}
} else {
url = monitorJSON["url"];
}
// If heartbeatJSON is not null, we go into the normal alerting loop.
if (heartbeatJSON["status"] == 0) {
let discorddowndata = {
username: discordDisplayName,
embeds: [{
title: "❌ Your service " + monitorJSON["name"] + " went down. ❌",
color: 16711680,
timestamp: heartbeatJSON["time"],
fields: [
{
name: "Service Name",
value: monitorJSON["name"],
},
{
name: "Service URL",
value: url,
},
{
name: "Time (UTC)",
value: heartbeatJSON["time"],
},
{
name: "Error",
value: heartbeatJSON["msg"],
},
],
}],
}
await axios.post(notification.discordWebhookUrl, discorddowndata)
return okMsg;
} else if (heartbeatJSON["status"] == 1) {
let discordupdata = {
username: discordDisplayName,
embeds: [{
title: "✅ Your service " + monitorJSON["name"] + " is up! ✅",
color: 65280,
timestamp: heartbeatJSON["time"],
fields: [
{
name: "Service Name",
value: monitorJSON["name"],
},
{
name: "Service URL",
value: url.startsWith("http") ? "[Visit Service](" + url + ")" : url,
},
{
name: "Time (UTC)",
value: heartbeatJSON["time"],
},
{
name: "Ping",
value: heartbeatJSON["ping"] + "ms",
},
],
}],
}
await axios.post(notification.discordWebhookUrl, discordupdata)
return okMsg;
}
} catch (error) {
throwGeneralAxiosError(error)
}
} else if (notification.type === "signal") {
try {
let data = {
"message": msg,
"number": notification.signalNumber,
"recipients": notification.signalRecipients.replace(/\s/g, "").split(","),
};
let config = {};
await axios.post(notification.signalURL, data, config)
return okMsg;
} catch (error) {
throwGeneralAxiosError(error)
}
} else if (notification.type === "pushy") {
try {
await axios.post(`https://api.pushy.me/push?api_key=${notification.pushyAPIKey}`, {
"to": notification.pushyToken,
"data": {
"message": "Uptime-Kuma"
},
"notification": {
"body": msg,
"badge": 1,
"sound": "ping.aiff"
}
})
return true;
} catch (error) {
console.log(error)
return false;
}
} else if (notification.type === "octopush") {
try {
let config = {
headers: {
"api-key": notification.octopushAPIKey,
"api-login": notification.octopushLogin,
"cache-control": "no-cache"
}
};
let data = {
"recipients": [
{
"phone_number": notification.octopushPhoneNumber
}
],
//octopush not supporting non ascii char
"text": msg.replace(/[^\x00-\x7F]/g, ""),
"type": notification.octopushSMSType,
"purpose": "alert",
"sender": notification.octopushSenderName
};
await axios.post("https://api.octopush.com/v1/public/sms-campaign/send", data, config)
return true;
} catch (error) {
console.log(error)
return false;
}
} else if (notification.type === "slack") {
try {
if (heartbeatJSON == null) {
let data = {
"text": "Uptime Kuma Slack testing successful.",
"channel": notification.slackchannel,
"username": notification.slackusername,
"icon_emoji": notification.slackiconemo,
}
await axios.post(notification.slackwebhookURL, data)
return okMsg;
}
const time = heartbeatJSON["time"];
let data = {
"text": "Uptime Kuma Alert",
"channel": notification.slackchannel,
"username": notification.slackusername,
"icon_emoji": notification.slackiconemo,
"blocks": [{
"type": "header",
"text": {
"type": "plain_text",
"text": "Uptime Kuma Alert",
},
},
{
"type": "section",
"fields": [{
"type": "mrkdwn",
"text": "*Message*\n" + msg,
},
{
"type": "mrkdwn",
"text": "*Time (UTC)*\n" + time,
}],
},
{
"type": "actions",
"elements": [
{
"type": "button",
"text": {
"type": "plain_text",
"text": "Visit Uptime Kuma",
},
"value": "Uptime-Kuma",
"url": notification.slackbutton || "https://github.com/louislam/uptime-kuma",
},
],
}],
}
await axios.post(notification.slackwebhookURL, data)
return okMsg;
} catch (error) {
throwGeneralAxiosError(error)
}
} else if (notification.type === "rocket.chat") {
try {
if (heartbeatJSON == null) {
let data = {
"text": "Uptime Kuma Rocket.chat testing successful.",
"channel": notification.rocketchannel,
"username": notification.rocketusername,
"icon_emoji": notification.rocketiconemo,
}
await axios.post(notification.rocketwebhookURL, data)
return okMsg;
}
const time = heartbeatJSON["time"];
let data = {
"text": "Uptime Kuma Alert",
"channel": notification.rocketchannel,
"username": notification.rocketusername,
"icon_emoji": notification.rocketiconemo,
"attachments": [
{
"title": "Uptime Kuma Alert *Time (UTC)*\n" + time,
"title_link": notification.rocketbutton,
"text": "*Message*\n" + msg,
"color": "#32cd32"
}
]
}
await axios.post(notification.rocketwebhookURL, data)
return okMsg;
} catch (error) {
throwGeneralAxiosError(error)
}
} else if (notification.type === "mattermost") {
try {
const mattermostUserName = notification.mattermostusername || "Uptime Kuma";
// If heartbeatJSON is null, assume we're testing.
if (heartbeatJSON == null) {
let mattermostTestData = {
username: mattermostUserName,
text: msg,
}
await axios.post(notification.mattermostWebhookUrl, mattermostTestData)
return okMsg;
}
const mattermostChannel = notification.mattermostchannel;
const mattermostIconEmoji = notification.mattermosticonemo;
const mattermostIconUrl = notification.mattermosticonurl;
if (heartbeatJSON["status"] == 0) {
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"] == 1) {
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;
}
} catch (error) {
throwGeneralAxiosError(error);
}
} else if (notification.type === "pushover") {
let pushoverlink = "https://api.pushover.net/1/messages.json"
try {
if (heartbeatJSON == null) {
let data = {
"message": "<b>Uptime Kuma Pushover testing successful.</b>",
"user": notification.pushoveruserkey,
"token": notification.pushoverapptoken,
"sound": notification.pushoversounds,
"priority": notification.pushoverpriority,
"title": notification.pushovertitle,
"retry": "30",
"expire": "3600",
"html": 1,
}
await axios.post(pushoverlink, data)
return okMsg;
}
let data = {
"message": "<b>Uptime Kuma Alert</b>\n\n<b>Message</b>:" + msg + "\n<b>Time (UTC)</b>:" + heartbeatJSON["time"],
"user": notification.pushoveruserkey,
"token": notification.pushoverapptoken,
"sound": notification.pushoversounds,
"priority": notification.pushoverpriority,
"title": notification.pushovertitle,
"retry": "30",
"expire": "3600",
"html": 1,
}
await axios.post(pushoverlink, data)
return okMsg;
} catch (error) {
throwGeneralAxiosError(error)
}
} else if (notification.type === "apprise") {
return Notification.apprise(notification, msg)
} else if (notification.type === "lunasea") {
let lunaseadevice = "https://notify.lunasea.app/v1/custom/device/" + notification.lunaseaDevice
try {
if (heartbeatJSON == null) {
let testdata = {
"title": "Uptime Kuma Alert",
"body": "Testing Successful.",
}
await axios.post(lunaseadevice, testdata)
return okMsg;
}
if (heartbeatJSON["status"] == 0) {
let downdata = {
"title": "UptimeKuma Alert:" + monitorJSON["name"],
"body": "[🔴 Down]" + heartbeatJSON["msg"] + "\nTime (UTC):" + heartbeatJSON["time"],
}
await axios.post(lunaseadevice, downdata)
return okMsg;
}
if (heartbeatJSON["status"] == 1) {
let updata = {
"title": "UptimeKuma Alert:" + monitorJSON["name"],
"body": "[✅ Up]" + heartbeatJSON["msg"] + "\nTime (UTC):" + heartbeatJSON["time"],
}
await axios.post(lunaseadevice, updata)
return okMsg;
}
} catch (error) {
throwGeneralAxiosError(error)
}
} else if (notification.type === "pushbullet") {
try {
let pushbulletUrl = "https://api.pushbullet.com/v2/pushes";
let config = {
headers: {
"Access-Token": notification.pushbulletAccessToken,
"Content-Type": "application/json"
}
};
if (heartbeatJSON == null) {
let testdata = {
"type": "note",
"title": "Uptime Kuma Alert",
"body": "Testing Successful.",
}
await axios.post(pushbulletUrl, testdata, config)
} else if (heartbeatJSON["status"] == 0) {
let downdata = {
"type": "note",
"title": "UptimeKuma Alert:" + monitorJSON["name"],
"body": "[🔴 Down]" + heartbeatJSON["msg"] + "\nTime (UTC):" + heartbeatJSON["time"],
}
await axios.post(pushbulletUrl, downdata, config)
} else if (heartbeatJSON["status"] == 1) {
let updata = {
"type": "note",
"title": "UptimeKuma Alert:" + monitorJSON["name"],
"body": "[✅ Up]" + heartbeatJSON["msg"] + "\nTime (UTC):" + heartbeatJSON["time"],
}
await axios.post(pushbulletUrl, updata, config)
}
return okMsg;
} catch (error) {
throwGeneralAxiosError(error)
}
} else if (notification.type === "line") {
try {
let lineAPIUrl = "https://api.line.me/v2/bot/message/push";
let config = {
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer " + notification.lineChannelAccessToken
}
};
if (heartbeatJSON == null) {
let testMessage = {
"to": notification.lineUserID,
"messages": [
{
"type": "text",
"text": "Test Successful!"
}
]
}
await axios.post(lineAPIUrl, testMessage, config)
} else if (heartbeatJSON["status"] == 0) {
let downMessage = {
"to": notification.lineUserID,
"messages": [
{
"type": "text",
"text": "UptimeKuma Alert: [🔴 Down]\n" + "Name: " + monitorJSON["name"] + " \n" + heartbeatJSON["msg"] + "\nTime (UTC): " + heartbeatJSON["time"]
}
]
}
await axios.post(lineAPIUrl, downMessage, config)
} else if (heartbeatJSON["status"] == 1) {
let upMessage = {
"to": notification.lineUserID,
"messages": [
{
"type": "text",
"text": "UptimeKuma Alert: [✅ Up]\n" + "Name: " + monitorJSON["name"] + " \n" + heartbeatJSON["msg"] + "\nTime (UTC): " + heartbeatJSON["time"]
}
]
}
await axios.post(lineAPIUrl, upMessage, config)
}
return okMsg;
} catch (error) {
throwGeneralAxiosError(error)
}
if (this.providerList[notification.type]) {
return this.providerList[notification.type].send(notification, msg, monitorJSON, heartbeatJSON);
} else {
throw new Error("Notification type is not supported")
throw new Error("Notification type is not supported");
}
}
@@ -617,8 +92,15 @@ class Notification {
bean.name = notification.name;
bean.user_id = userID;
bean.config = JSON.stringify(notification)
bean.config = JSON.stringify(notification);
bean.is_default = notification.isDefault || false;
await R.store(bean)
if (notification.applyExisting) {
await applyNotificationEveryMonitor(bean.id, userID);
}
return bean;
}
static async delete(notificationID, userID) {
@@ -634,52 +116,6 @@ class Notification {
await R.trash(bean)
}
static async smtp(notification, msg) {
const config = {
host: notification.smtpHost,
port: notification.smtpPort,
secure: notification.smtpSecure,
};
// Should fix the issue in https://github.com/louislam/uptime-kuma/issues/26#issuecomment-896373904
if (notification.smtpUsername || notification.smtpPassword) {
config.auth = {
user: notification.smtpUsername,
pass: notification.smtpPassword,
};
}
let transporter = nodemailer.createTransport(config);
// send mail with defined transport object
await transporter.sendMail({
from: `"Uptime Kuma" <${notification.smtpFrom}>`,
to: notification.smtpTo,
subject: msg,
text: msg,
});
return "Sent Successfully.";
}
static async apprise(notification, msg) {
let s = child_process.spawnSync("apprise", [ "-vv", "-b", msg, notification.appriseURL])
let output = (s.stdout) ? s.stdout.toString() : "ERROR: maybe apprise not found";
if (output) {
if (! output.includes("ERROR")) {
return "Sent Successfully";
}
throw new Error(output)
} else {
return ""
}
}
static checkApprise() {
let commandExistsSync = require("command-exists").sync;
let exists = commandExistsSync("apprise");
@@ -688,18 +124,24 @@ class Notification {
}
function throwGeneralAxiosError(error) {
let msg = "Error: " + error + " ";
async function applyNotificationEveryMonitor(notificationID, userID) {
let monitors = await R.getAll("SELECT id FROM monitor WHERE user_id = ?", [
userID
]);
if (error.response && error.response.data) {
if (typeof error.response.data === "string") {
msg += error.response.data;
} else {
msg += JSON.stringify(error.response.data)
for (let i = 0; i < monitors.length; i++) {
let checkNotification = await R.findOne("monitor_notification", " monitor_id = ? AND notification_id = ? ", [
monitors[i].id,
notificationID,
])
if (! checkNotification) {
let relation = R.dispense("monitor_notification");
relation.monitor_id = monitors[i].id;
relation.notification_id = notificationID;
await R.store(relation)
}
}
throw new Error(msg)
}
module.exports = {

View File

@@ -6,6 +6,7 @@ const { sleep, debug, TimeLogger, getRandomInt } = require("../src/util");
console.log("Importing Node libraries")
const fs = require("fs");
const http = require("http");
const https = require("https");
console.log("Importing 3rd-party libraries")
debug("Importing express");
@@ -26,8 +27,11 @@ debug("Importing Monitor");
const Monitor = require("./model/monitor");
debug("Importing Settings");
const { getSettings, setSettings, setting, initJWTSecret } = require("./util-server");
debug("Importing Notification");
const { Notification } = require("./notification");
Notification.init();
debug("Importing Database");
const Database = require("./database");
@@ -45,11 +49,48 @@ console.info("Version: " + checkVersion.version);
const hostname = process.env.HOST || args.host;
const port = parseInt(process.env.PORT || args.port || 3001);
// SSL
const sslKey = process.env.SSL_KEY || args["ssl-key"] || undefined;
const sslCert = process.env.SSL_CERT || args["ssl-cert"] || undefined;
// Demo Mode?
const demoMode = args["demo"] || false;
if (demoMode) {
console.log("==== Demo Mode ====");
}
// Data Directory (must be end with "/")
Database.dataDir = process.env.DATA_DIR || args["data-dir"] || "./data/";
Database.path = Database.dataDir + "kuma.db";
if (! fs.existsSync(Database.dataDir)) {
fs.mkdirSync(Database.dataDir, { recursive: true });
}
console.log(`Data Dir: ${Database.dataDir}`);
console.log("Creating express and socket.io instance")
const app = express();
const server = http.createServer(app);
let server;
if (sslKey && sslCert) {
console.log("Server Type: HTTPS");
server = https.createServer({
key: fs.readFileSync(sslKey),
cert: fs.readFileSync(sslCert)
}, app);
} else {
console.log("Server Type: HTTP");
server = http.createServer(app);
}
const io = new Server(server);
app.use(express.json())
module.exports.io = io;
// Must be after io instantiation
const { sendNotificationList, sendHeartbeatList, sendImportantHeartbeatList } = require("./client");
app.use(express.json());
/**
* Total WebSocket client connected to server currently, no actual use
@@ -486,12 +527,13 @@ let indexHTML = fs.readFileSync("./dist/index.html").toString();
try {
checkLogin(socket)
await Notification.save(notification, notificationID, socket.userID)
let notificationBean = await Notification.save(notification, notificationID, socket.userID)
await sendNotificationList(socket)
callback({
ok: true,
msg: "Saved",
id: notificationBean.id,
});
} catch (e) {
@@ -552,6 +594,152 @@ let indexHTML = fs.readFileSync("./dist/index.html").toString();
}
});
socket.on("uploadBackup", async (uploadedJSON, callback) => {
try {
checkLogin(socket)
let backupData = JSON.parse(uploadedJSON);
console.log(`Importing Backup, User ID: ${socket.userID}, Version: ${backupData.version}`)
let notificationList = backupData.notificationList;
let monitorList = backupData.monitorList;
if (notificationList.length >= 1) {
for (let i = 0; i < notificationList.length; i++) {
let notification = JSON.parse(notificationList[i].config);
await Notification.save(notification, null, socket.userID)
}
}
if (monitorList.length >= 1) {
for (let i = 0; i < monitorList.length; i++) {
let monitor = {
name: monitorList[i].name,
type: monitorList[i].type,
url: monitorList[i].url,
interval: monitorList[i].interval,
hostname: monitorList[i].hostname,
maxretries: monitorList[i].maxretries,
port: monitorList[i].port,
keyword: monitorList[i].keyword,
ignoreTls: monitorList[i].ignoreTls,
upsideDown: monitorList[i].upsideDown,
maxredirects: monitorList[i].maxredirects,
accepted_statuscodes: monitorList[i].accepted_statuscodes,
dns_resolve_type: monitorList[i].dns_resolve_type,
dns_resolve_server: monitorList[i].dns_resolve_server,
notificationIDList: {},
}
let bean = R.dispense("monitor")
let notificationIDList = monitor.notificationIDList;
delete monitor.notificationIDList;
monitor.accepted_statuscodes_json = JSON.stringify(monitor.accepted_statuscodes);
delete monitor.accepted_statuscodes;
bean.import(monitor)
bean.user_id = socket.userID
await R.store(bean)
await updateMonitorNotification(bean.id, notificationIDList)
if (monitorList[i].active == 1) {
await startMonitor(socket.userID, bean.id);
} else {
await pauseMonitor(socket.userID, bean.id);
}
}
await sendNotificationList(socket)
await sendMonitorList(socket);
}
callback({
ok: true,
msg: "Backup successfully restored.",
});
} catch (e) {
callback({
ok: false,
msg: e.message,
});
}
});
socket.on("clearEvents", async (monitorID, callback) => {
try {
checkLogin(socket)
console.log(`Clear Events Monitor: ${monitorID} User ID: ${socket.userID}`)
await R.exec("UPDATE heartbeat SET msg = ?, important = ? WHERE monitor_id = ? ", [
"",
"0",
monitorID,
]);
await sendImportantHeartbeatList(socket, monitorID, true, true);
callback({
ok: true,
});
} catch (e) {
callback({
ok: false,
msg: e.message,
});
}
});
socket.on("clearHeartbeats", async (monitorID, callback) => {
try {
checkLogin(socket)
console.log(`Clear Heartbeats Monitor: ${monitorID} User ID: ${socket.userID}`)
await R.exec("DELETE FROM heartbeat WHERE monitor_id = ?", [
monitorID
]);
await sendHeartbeatList(socket, monitorID, true, true);
callback({
ok: true,
});
} catch (e) {
callback({
ok: false,
msg: e.message,
});
}
});
socket.on("clearStatistics", async (callback) => {
try {
checkLogin(socket)
console.log(`Clear Statistics User ID: ${socket.userID}`)
await R.exec("DELETE FROM heartbeat");
callback({
ok: true,
});
} catch (e) {
callback({
ok: false,
msg: e.message,
});
}
});
debug("added all socket handlers")
// ***************************
@@ -620,25 +808,6 @@ async function sendMonitorList(socket) {
return list;
}
async function sendNotificationList(socket) {
const timeLogger = new TimeLogger();
let result = [];
let list = await R.find("notification", " user_id = ? ", [
socket.userID,
]);
for (let bean of list) {
result.push(bean.export())
}
io.to(socket.userID).emit("notificationList", result)
timeLogger.print("Send Notification List");
return list;
}
async function afterLogin(socket, user) {
socket.userID = user.id;
socket.join(user.id)
@@ -773,48 +942,6 @@ async function startMonitors() {
}
}
/**
* Send Heartbeat History list to socket
*/
async function sendHeartbeatList(socket, monitorID) {
const timeLogger = new TimeLogger();
let list = await R.find("heartbeat", `
monitor_id = ?
ORDER BY time DESC
LIMIT 100
`, [
monitorID,
])
let result = [];
for (let bean of list) {
result.unshift(bean.toJSON())
}
socket.emit("heartbeatList", monitorID, result)
timeLogger.print(`[Monitor: ${monitorID}] sendHeartbeatList`)
}
async function sendImportantHeartbeatList(socket, monitorID) {
const timeLogger = new TimeLogger();
let list = await R.find("heartbeat", `
monitor_id = ?
AND important = 1
ORDER BY time DESC
LIMIT 500
`, [
monitorID,
])
timeLogger.print(`[Monitor: ${monitorID}] sendImportantHeartbeatList`);
socket.emit("importantHeartbeatList", monitorID, list)
}
async function shutdownFunction(signal) {
console.log("Shutdown requested");
console.log("Called signal: " + signal);

View File

@@ -71,6 +71,14 @@ h2 {
}
}
.btn-warning {
color: white;
&:hover, &:active, &:focus, &.active {
color: white;
}
}
.btn-info {
color: white;
@@ -144,7 +152,7 @@ h2 {
}
.form-switch .form-check-input {
background-color: #131a21;
background-color: #232f3b;
}
a,
@@ -186,6 +194,14 @@ h2 {
color: white;
}
.btn-warning {
color: $dark-font-color2;
&:hover, &:active, &:focus, &.active {
color: $dark-font-color2;
}
}
.btn-close {
box-shadow: none;
filter: invert(1);

View File

@@ -16,3 +16,5 @@ $dark-border-color: #1d2634;
$easing-in: cubic-bezier(0.54, 0.78, 0.55, 0.97);
$easing-out: cubic-bezier(0.25, 0.46, 0.45, 0.94);
$easing-in-out: cubic-bezier(0.79, 0.14, 0.15, 0.86);
$dropdown-border-radius: 0.5rem;

View File

@@ -31,7 +31,7 @@ export default {
beatWidth: 10,
beatHeight: 30,
hoverScale: 1.5,
beatMargin: 3, // Odd number only, even = blurry
beatMargin: 4,
move: false,
maxBeat: -1,
}
@@ -122,11 +122,25 @@ export default {
this.$root.heartbeatList[this.monitorId] = [];
}
},
mounted() {
if (this.size === "small") {
this.beatWidth = 5.6;
this.beatMargin = 2.4;
this.beatHeight = 16
this.beatWidth = 5;
this.beatHeight = 16;
this.beatMargin = 2;
}
// Suddenly, have an idea how to handle it universally.
// If the pixel * ratio != Integer, then it causes render issue, round it to solve it!!
const actualWidth = this.beatWidth * window.devicePixelRatio;
const actualMargin = this.beatMargin * window.devicePixelRatio;
if (! Number.isInteger(actualWidth)) {
this.beatWidth = Math.round(actualWidth) / window.devicePixelRatio;
}
if (! Number.isInteger(actualMargin)) {
this.beatMargin = Math.round(actualMargin) / window.devicePixelRatio;
}
window.addEventListener("resize", this.resize);

View File

@@ -0,0 +1,78 @@
<template>
<div class="input-group mb-3">
<input
ref="input"
v-model="model"
:type="visibility"
class="form-control"
:placeholder="placeholder"
:maxlength="maxlength"
:autocomplete="autocomplete"
:required="required"
:readonly="readonly"
>
<a v-if="visibility == 'password'" class="btn btn-outline-primary" @click="showInput()">
<font-awesome-icon icon="eye" />
</a>
<a v-if="visibility == 'text'" class="btn btn-outline-primary" @click="hideInput()">
<font-awesome-icon icon="eye-slash" />
</a>
</div>
</template>
<script>
export default {
props: {
modelValue: {
type: String,
default: ""
},
placeholder: {
type: String,
default: ""
},
maxlength: {
type: Number,
default: 255
},
autocomplete: {
type: String,
default: undefined,
},
required: {
type: Boolean
},
readonly: {
type: String,
default: undefined,
},
},
data() {
return {
visibility: "password",
}
},
computed: {
model: {
get() {
return this.modelValue
},
set(value) {
this.$emit("update:modelValue", value)
}
}
},
created() {
},
methods: {
showInput() {
this.visibility = "text";
},
hideInput() {
this.visibility = "password";
},
}
}
</script>

View File

@@ -11,8 +11,8 @@
</div>
<div class="modal-body">
<div class="mb-3">
<label for="type" class="form-label">{{ $t("Notification Type") }}</label>
<select id="type" v-model="notification.type" class="form-select">
<label for="notification-type" class="form-label">{{ $t("Notification Type") }}</label>
<select id="notification-type" v-model="notification.type" class="form-select">
<option value="telegram">Telegram</option>
<option value="webhook">Webhook</option>
<option value="smtp">{{ $t("Email") }} (SMTP)</option>
@@ -33,48 +33,13 @@
</div>
<div class="mb-3">
<label for="name" class="form-label">{{ $t("Friendly Name") }}</label>
<input id="name" v-model="notification.name" type="text" class="form-control" required>
<label for="notification-name" class="form-label">{{ $t("Friendly Name") }}</label>
<input id="notification-name" v-model="notification.name" type="text" class="form-control" required>
</div>
<template v-if="notification.type === 'telegram'">
<div class="mb-3">
<label for="telegram-bot-token" class="form-label">Bot Token</label>
<input id="telegram-bot-token" v-model="notification.telegramBotToken" type="text" class="form-control" required>
<div class="form-text">
You can get a token from <a href="https://t.me/BotFather" target="_blank">https://t.me/BotFather</a>.
</div>
</div>
<Telegram v-if="notification.type === 'telegram'" />
<div class="mb-3">
<label for="telegram-chat-id" class="form-label">Chat ID</label>
<div class="input-group mb-3">
<input id="telegram-chat-id" v-model="notification.telegramChatID" type="text" class="form-control" required>
<button v-if="notification.telegramBotToken" class="btn btn-outline-secondary" type="button" @click="autoGetTelegramChatID">
Auto Get
</button>
</div>
<div class="form-text">
Support Direct Chat / Group / Channel's Chat ID
<p style="margin-top: 8px;">
You can get your chat id by sending message to the bot and go to this url to view the chat_id:
</p>
<p style="margin-top: 8px;">
<template v-if="notification.telegramBotToken">
<a :href="telegramGetUpdatesURL" target="_blank" style="word-break: break-word;">{{ telegramGetUpdatesURL }}</a>
</template>
<template v-else>
{{ telegramGetUpdatesURL }}
</template>
</p>
</div>
</div>
</template>
<!-- TODO: Convert all into vue components, but not an easy task. -->
<template v-if="notification.type === 'webhook'">
<div class="mb-3">
@@ -100,49 +65,7 @@
</div>
</template>
<template v-if="notification.type === 'smtp'">
<div class="mb-3">
<label for="hostname" class="form-label">Hostname</label>
<input id="hostname" v-model="notification.smtpHost" type="text" class="form-control" required>
</div>
<div class="mb-3">
<label for="port" class="form-label">Port</label>
<input id="port" v-model="notification.smtpPort" type="number" class="form-control" required min="0" max="65535" step="1">
</div>
<div class="mb-3">
<div class="form-check">
<input id="secure" v-model="notification.smtpSecure" class="form-check-input" type="checkbox" value="">
<label class="form-check-label" for="secure">
Secure
</label>
</div>
<div class="form-text">
Generally, true for 465, false for other ports.
</div>
</div>
<div class="mb-3">
<label for="username" class="form-label">Username</label>
<input id="username" v-model="notification.smtpUsername" type="text" class="form-control" autocomplete="false">
</div>
<div class="mb-3">
<label for="password" class="form-label">Password</label>
<input id="password" v-model="notification.smtpPassword" type="password" class="form-control" autocomplete="false">
</div>
<div class="mb-3">
<label for="from-email" class="form-label">From Email</label>
<input id="from-email" v-model="notification.smtpFrom" type="email" class="form-control" required autocomplete="false">
</div>
<div class="mb-3">
<label for="to-email" class="form-label">To Email</label>
<input id="to-email" v-model="notification.smtpTo" type="email" class="form-control" required autocomplete="false">
</div>
</template>
<SMTP v-if="notification.type === 'smtp'" />
<template v-if="notification.type === 'discord'">
<div class="mb-3">
@@ -195,7 +118,7 @@
<template v-if="notification.type === 'gotify'">
<div class="mb-3">
<label for="gotify-application-token" class="form-label">Application Token</label>
<input id="gotify-application-token" v-model="notification.gotifyapplicationToken" type="text" class="form-control" required>
<HiddenInput id="gotify-application-token" v-model="notification.gotifyapplicationToken" :required="true" autocomplete="one-time-code"></HiddenInput>
</div>
<div class="mb-3">
<label for="gotify-server-url" class="form-label">Server URL</label>
@@ -306,13 +229,13 @@
<template v-if="notification.type === 'pushy'">
<div class="mb-3">
<label for="pushy-app-token" class="form-label">API_KEY</label>
<input id="pushy-app-token" v-model="notification.pushyAPIKey" type="text" class="form-control" required>
<HiddenInput id="pushy-app-token" v-model="notification.pushyAPIKey" :required="true" autocomplete="one-time-code"></HiddenInput>
</div>
<div class="mb-3">
<label for="pushy-user-key" class="form-label">USER_TOKEN</label>
<div class="input-group mb-3">
<input id="pushy-user-key" v-model="notification.pushyToken" type="text" class="form-control" required>
<HiddenInput id="pushy-user-key" v-model="notification.pushyToken" :required="true" autocomplete="one-time-code"></HiddenInput>
</div>
</div>
<p style="margin-top: 8px;">
@@ -323,7 +246,7 @@
<template v-if="notification.type === 'octopush'">
<div class="mb-3">
<label for="octopush-key" class="form-label">API KEY</label>
<input id="octopush-key" v-model="notification.octopushAPIKey" type="text" class="form-control" required>
<HiddenInput id="octopush-key" v-model="notification.octopushAPIKey" :required="true" autocomplete="one-time-code"></HiddenInput>
<label for="octopush-login" class="form-label">API LOGIN</label>
<input id="octopush-login" v-model="notification.octopushLogin" type="text" class="form-control" required>
</div>
@@ -354,9 +277,9 @@
<template v-if="notification.type === 'pushover'">
<div class="mb-3">
<label for="pushover-user" class="form-label">User Key<span style="color: red;"><sup>*</sup></span></label>
<input id="pushover-user" v-model="notification.pushoveruserkey" type="text" class="form-control" required>
<HiddenInput id="pushover-user" v-model="notification.pushoveruserkey" :required="true" autocomplete="one-time-code"></HiddenInput>
<label for="pushover-app-token" class="form-label">Application Token<span style="color: red;"><sup>*</sup></span></label>
<input id="pushover-app-token" v-model="notification.pushoverapptoken" type="text" class="form-control" required>
<HiddenInput id="pushover-app-token" v-model="notification.pushoverapptoken" :required="true" autocomplete="one-time-code"></HiddenInput>
<label for="pushover-device" class="form-label">Device</label>
<input id="pushover-device" v-model="notification.pushoverdevice" type="text" class="form-control">
<label for="pushover-device" class="form-label">Message Title</label>
@@ -442,7 +365,7 @@
<template v-if="notification.type === 'pushbullet'">
<div class="mb-3">
<label for="pushbullet-access-token" class="form-label">Access Token</label>
<input id="pushbullet-access-token" v-model="notification.pushbulletAccessToken" type="text" class="form-control" required>
<HiddenInput id="pushbullet-access-token" v-model="notification.pushbulletAccessToken" :required="true" autocomplete="one-time-code"></HiddenInput>
</div>
<p style="margin-top: 8px;">
@@ -453,7 +376,7 @@
<template v-if="notification.type === 'line'">
<div class="mb-3">
<label for="line-channel-access-token" class="form-label">Channel access token</label>
<input id="line-channel-access-token" v-model="notification.lineChannelAccessToken" type="text" class="form-control" required>
<HiddenInput id="line-channel-access-token" v-model="notification.lineChannelAccessToken" :required="true" autocomplete="one-time-code"></HiddenInput>
</div>
<div class="form-text">
Line Developers Console - <b>Basic Settings</b>
@@ -469,7 +392,29 @@
First access the <a href="https://developers.line.biz/console/" target="_blank">Line Developers Console</a>, create a provider and channel (Messaging API), then you can get the channel access token and user id from the above mentioned menu items.
</div>
</template>
<!-- DEPRECATED! Please create vue component in "./src/components/notifications/{notification name}.vue" -->
<div class="mb-3 mt-4">
<hr class="dropdown-divider mb-4">
<div class="form-check form-switch">
<input v-model="notification.isDefault" class="form-check-input" type="checkbox">
<label class="form-check-label">{{ $t("Default enabled") }}</label>
</div>
<div class="form-text">
{{ $t("enableDefaultNotificationDescription") }}
</div>
<br>
<div class="form-check form-switch">
<input v-model="notification.applyExisting" class="form-check-input" type="checkbox">
<label class="form-check-label">{{ $t("Also apply to existing monitors") }}</label>
</div>
</div>
</div>
<div class="modal-footer">
<button v-if="id" type="button" class="btn btn-danger" :disabled="processing" @click="deleteConfirm">
{{ $t("Delete") }}
@@ -478,6 +423,7 @@
{{ $t("Test") }}
</button>
<button type="submit" class="btn btn-primary" :disabled="processing">
<div v-if="processing" class="spinner-border spinner-border-sm me-1"></div>
{{ $t("Save") }}
</button>
</div>
@@ -494,16 +440,21 @@
<script lang="ts">
import { Modal } from "bootstrap"
import { ucfirst } from "../util.ts"
import axios from "axios";
import { useToast } from "vue-toastification"
import Confirm from "./Confirm.vue";
const toast = useToast()
import HiddenInput from "./HiddenInput.vue";
import Telegram from "./notifications/Telegram.vue";
import SMTP from "./notifications/SMTP.vue";
export default {
components: {
Confirm,
HiddenInput,
Telegram,
SMTP,
},
props: {},
emits: ["added"],
data() {
return {
model: null,
@@ -512,22 +463,13 @@ export default {
notification: {
name: "",
type: null,
gotifyPriority: 8,
isDefault: false,
// Do not set default value here, please scroll to show()
},
appriseInstalled: false,
}
},
computed: {
telegramGetUpdatesURL() {
let token = "<YOUR BOT TOKEN HERE>"
if (this.notification.telegramBotToken) {
token = this.notification.telegramBotToken;
}
return `https://api.telegram.org/bot${token}/getUpdates`;
},
},
watch: {
"notification.type"(to, from) {
let oldName;
@@ -572,11 +514,13 @@ export default {
this.notification = {
name: "",
type: null,
isDefault: false,
}
// Default set to Telegram
this.notification.type = "telegram"
this.notification.gotifyPriority = 8
// Set Default value here
this.notification.type = "telegram";
this.notification.gotifyPriority = 8;
this.notification.smtpSecure = false;
}
this.modal.show()
@@ -589,7 +533,13 @@ export default {
this.processing = false;
if (res.ok) {
this.modal.hide()
this.modal.hide();
// Emit added event, doesn't emit edit.
if (! this.id) {
this.$emit("added", res.id);
}
}
})
},
@@ -613,32 +563,6 @@ export default {
}
})
},
async autoGetTelegramChatID() {
try {
let res = await axios.get(this.telegramGetUpdatesURL)
if (res.data.result.length >= 1) {
let update = res.data.result[res.data.result.length - 1]
if (update.channel_post) {
this.notification.telegramChatID = update.channel_post.chat.id;
} else if (update.message) {
this.notification.telegramChatID = update.message.chat.id;
} else {
throw new Error("Chat ID is not found, please send a message to this bot first")
}
} else {
throw new Error("Chat ID is not found, please send a message to this bot first")
}
} catch (error) {
toast.error(error.message)
}
},
},
}
</script>

View File

@@ -85,7 +85,7 @@ export default {
y: {
title: {
display: true,
text: "Resp. Time (ms)",
text: this.$t("respTime"),
},
offset: false,
grid: {

View File

@@ -22,7 +22,7 @@ export default {
return Math.round(this.$root.uptimeList[key] * 10000) / 100 + "%";
}
return "N/A"
return this.$t("notAvailableShort")
},
color() {

View File

@@ -0,0 +1,75 @@
<template>
<div class="mb-3">
<label for="hostname" class="form-label">{{ $t("Hostname") }}</label>
<input id="hostname" v-model="$parent.notification.smtpHost" type="text" class="form-control" required>
</div>
<div class="mb-3">
<label for="port" class="form-label">{{ $t("Port") }}</label>
<input id="port" v-model="$parent.notification.smtpPort" type="number" class="form-control" required min="0" max="65535" step="1">
</div>
<div class="mb-3">
<label for="secure" class="form-label">Secure</label>
<select id="secure" v-model="$parent.notification.smtpSecure" class="form-select">
<option :value="false">None / STARTTLS (25, 587)</option>
<option :value="true">TLS (465)</option>
</select>
</div>
<div class="mb-3">
<div class="form-check">
<input id="ignore-tls-error" v-model="$parent.notification.smtpIgnoreTLSError" class="form-check-input" type="checkbox" value="">
<label class="form-check-label" for="ignore-tls-error">
Ignore TLS Error
</label>
</div>
</div>
<div class="mb-3">
<label for="username" class="form-label">{{ $t("Username") }}</label>
<input id="username" v-model="$parent.notification.smtpUsername" type="text" class="form-control" autocomplete="false">
</div>
<div class="mb-3">
<label for="password" class="form-label">{{ $t("Password") }}</label>
<HiddenInput id="password" v-model="$parent.notification.smtpPassword" :required="true" autocomplete="one-time-code"></HiddenInput>
</div>
<div class="mb-3">
<label for="from-email" class="form-label">From Email</label>
<input id="from-email" v-model="$parent.notification.smtpFrom" type="text" class="form-control" required autocomplete="false" placeholder="&quot;Uptime Kuma&quot; &lt;example@kuma.pet&gt;">
<div class="form-text">
</div>
</div>
<div class="mb-3">
<label for="to-email" class="form-label">To Email</label>
<input id="to-email" v-model="$parent.notification.smtpTo" type="text" class="form-control" required autocomplete="false" placeholder="example2@kuma.pet, example3@kuma.pet">
</div>
<div class="mb-3">
<label for="to-cc" class="form-label">CC</label>
<input id="to-cc" v-model="$parent.notification.smtpCC" type="text" class="form-control" autocomplete="false">
</div>
<div class="mb-3">
<label for="to-bcc" class="form-label">BCC</label>
<input id="to-bcc" v-model="$parent.notification.smtpBCC" type="text" class="form-control" autocomplete="false">
</div>
</template>
<script>
import HiddenInput from "../HiddenInput.vue";
export default {
components: {
HiddenInput,
},
data() {
return {
name: "smtp",
}
},
}
</script>

View File

@@ -0,0 +1,96 @@
<template>
<div class="mb-3">
<label for="telegram-bot-token" class="form-label">Bot Token</label>
<HiddenInput id="telegram-bot-token" v-model="$parent.notification.telegramBotToken" :required="true" autocomplete="one-time-code"></HiddenInput>
<div class="form-text">
You can get a token from <a href="https://t.me/BotFather" target="_blank">https://t.me/BotFather</a>.
</div>
</div>
<div class="mb-3">
<label for="telegram-chat-id" class="form-label">Chat ID</label>
<div class="input-group mb-3">
<input id="telegram-chat-id" v-model="$parent.notification.telegramChatID" type="text" class="form-control" required>
<button v-if="$parent.notification.telegramBotToken" class="btn btn-outline-secondary" type="button" @click="autoGetTelegramChatID">
{{ $t("Auto Get") }}
</button>
</div>
<div class="form-text">
Support Direct Chat / Group / Channel's Chat ID
<p style="margin-top: 8px;">
You can get your chat id by sending message to the bot and go to this url to view the chat_id:
</p>
<p style="margin-top: 8px;">
<template v-if="$parent.notification.telegramBotToken">
<a :href="telegramGetUpdatesURL" target="_blank" style="word-break: break-word;">{{ telegramGetUpdatesURL }}</a>
</template>
<template v-else>
{{ telegramGetUpdatesURL }}
</template>
</p>
</div>
</div>
</template>
<script>
import HiddenInput from "../HiddenInput.vue";
import axios from "axios";
import { useToast } from "vue-toastification"
const toast = useToast();
export default {
components: {
HiddenInput,
},
data() {
return {
name: "telegram",
}
},
computed: {
telegramGetUpdatesURL() {
let token = "<YOUR BOT TOKEN HERE>"
if (this.$parent.notification.telegramBotToken) {
token = this.$parent.notification.telegramBotToken;
}
return `https://api.telegram.org/bot${token}/getUpdates`;
},
},
mounted() {
},
methods: {
async autoGetTelegramChatID() {
try {
let res = await axios.get(this.telegramGetUpdatesURL)
if (res.data.result.length >= 1) {
let update = res.data.result[res.data.result.length - 1]
if (update.channel_post) {
this.notification.telegramChatID = update.channel_post.chat.id;
} else if (update.message) {
this.notification.telegramChatID = update.message.chat.id;
} else {
throw new Error("Chat ID is not found, please send a message to this bot first")
}
} else {
throw new Error("Chat ID is not found, please send a message to this bot first")
}
} catch (error) {
toast.error(error.message)
}
},
}
}
</script>

View File

@@ -1,10 +1,10 @@
import { library } from "@fortawesome/fontawesome-svg-core"
import { faCog, faEdit, faPlus, faPause, faPlay, faTachometerAlt, faTrash, faList, faArrowAltCircleUp } from "@fortawesome/free-solid-svg-icons"
import { faCog, faEdit, faPlus, faPause, faPlay, faTachometerAlt, faTrash, faList, faArrowAltCircleUp, faEye, faEyeSlash } from "@fortawesome/free-solid-svg-icons"
//import { fa } from '@fortawesome/free-regular-svg-icons'
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome"
// Add Free Font Awesome Icons here
// https://fontawesome.com/v5.15/icons?d=gallery&p=2&s=solid&m=free
library.add(faCog, faEdit, faPlus, faPause, faPlay, faTachometerAlt, faTrash, faList, faArrowAltCircleUp);
library.add(faCog, faEdit, faPlus, faPause, faPlay, faTachometerAlt, faTrash, faList, faArrowAltCircleUp, faEye, faEyeSlash);
export { FontAwesomeIcon }

18
src/languages/README.md Normal file
View File

@@ -0,0 +1,18 @@
# How to translate
1. Fork this repo.
2. Create a language file. (e.g. `zh-TW.js`) The filename must be ISO language code: http://www.lingoes.net/en/translator/langcode.htm
3. `npm run update-language-files --base-lang=de-DE`
6. Your language file should be filled in. You can translate now.
7. Translate `src/pages/Settings.vue` (search for a `Confirm` component with `rel="confirmDisableAuth"`).
8. Import your language file in `src/main.js` and add it to `languageList` constant.
9. Make a [pull request](https://github.com/louislam/uptime-kuma/pulls) when you have done.
One of good examples:
https://github.com/louislam/uptime-kuma/pull/316/files
If you do not have programming skills, let me know in [Issues section](https://github.com/louislam/uptime-kuma/issues). I will assist you. 😏

131
src/languages/da-DK.js Normal file
View File

@@ -0,0 +1,131 @@
export default {
languageName: "Danish",
Settings: "Indstillinger",
Dashboard: "Dashboard",
"New Update": "Opdatering tilgængelig",
Language: "Sprog",
Appearance: "Udseende",
Theme: "Tema",
General: "Generelt",
Version: "Version",
"Check Update On GitHub": "Tjek efter opdateringer på Github",
List: "Liste",
Add: "Tilføj",
"Add New Monitor": "Tilføj ny Overvåger",
"Quick Stats": "Oversigt",
Up: "Aktiv",
Down: "Inaktiv",
Pending: "Afventer",
Unknown: "Ukendt",
Pause: "Pause",
pauseDashboardHome: "Pauset",
Name: "Navn",
Status: "Status",
DateTime: "Dato / Tid",
Message: "Beskeder",
"No important events": "Inden vigtige begivenheder",
Resume: "Fortsæt",
Edit: "Rediger",
Delete: "Slet",
Current: "Aktuelt",
Uptime: "Oppetid",
"Cert Exp.": "Certifikatets udløb",
days: "Dage",
day: "Dag",
"-day": "-Dage",
hour: "Timer",
"-hour": "-Timer",
checkEverySecond: "Tjek hvert {0} sekund",
"Avg.": "Gennemsnit",
Response: " Respons",
Ping: "Ping",
"Monitor Type": "Overvåger Type",
Keyword: "Nøgleord",
"Friendly Name": "Visningsnavn",
URL: "URL",
Hostname: "Hostname",
Port: "Port",
"Heartbeat Interval": "Taktinterval",
Retries: "Gentagelser",
retriesDescription: "Maksimalt antal gentagelser, før tjenesten markeres som inaktiv og sender en meddelelse.",
Advanced: "Avanceret",
ignoreTLSError: "Ignorere TLS/SSL web fejl",
"Upside Down Mode": "Omvendt tilstand",
upsideDownModeDescription: "Håndter tilstanden omvendt. Hvis tjenesten er tilgængelig, vises den som inaktiv.",
"Max. Redirects": "Maks. Omdirigeringer",
maxRedirectDescription: "Maksimalt antal omdirigeringer, der skal følges. Indstil til 0 for at deaktivere omdirigeringer.",
"Accepted Status Codes": "Tilladte HTTP-Statuskoder",
acceptedStatusCodesDescription: "Vælg de statuskoder, der stadig skal vurderes som vellykkede.",
Save: "Gem",
Notifications: "Underretninger",
"Not available, please setup.": "Ikke tilgængelige, opsæt venligst.",
"Setup Notification": "Opsæt underretninger",
Light: "Lys",
Dark: "Mørk",
Auto: "Auto",
"Theme - Heartbeat Bar": "Tema - Tidslinje",
Normal: "Normal",
Bottom: "Bunden",
None: "Ingen",
Timezone: "Tidszone",
"Search Engine Visibility": "Søgemaskine synlighed",
"Allow indexing": "Tillad indeksering",
"Discourage search engines from indexing site": "Frabed søgemaskiner at indeksere webstedet",
"Change Password": "Ændre adgangskode",
"Current Password": "Nuværende adgangskode",
"New Password": "Ny adgangskode",
"Repeat New Password": "Gentag den nye adgangskode",
passwordNotMatchMsg: "Adgangskoderne er ikke ens.",
"Update Password": "Opdater adgangskode",
"Disable Auth": "Deaktiver autentificering",
"Enable Auth": "Aktiver autentificering",
Logout: "Log ud",
notificationDescription: "Tildel underretninger til Overvåger(e), så denne funktion træder i kraft.",
Leave: "Verlassen",
"I understand, please disable": "Jeg er indforstået, deaktiver venligst",
Confirm: "Bekræft",
Yes: "Ja",
No: "Nej",
Username: "Brugernavn",
Password: "Adgangskode",
"Remember me": "Husk mig",
Login: "Log ind",
"No Monitors, please": "Ingen Overvågere",
"add one": "tilføj en",
"Notification Type": "Underretningstype",
Email: "E-Mail",
Test: "Test",
"Certificate Info": "Certifikatoplysninger",
keywordDescription: "Søg efter et søgeord i almindelig HTML- eller JSON -output. Bemærk, at der skelnes mellem store og små bogstaver.",
deleteMonitorMsg: "Er du sikker på, at du vil slette overvågeren?",
deleteNotificationMsg: "Er du sikker på, at du vil slette denne underretning for alle overvågere? ",
resoverserverDescription: "Cloudflare er standardserveren, den kan til enhver tid ændres.",
"Resolver Server": "Navne-server",
rrtypeDescription: "Vælg den type RR, du vil overvåge.",
"Last Result": "Seneste resultat",
pauseMonitorMsg: "Er du sikker på, at du vil pause Overvågeren?",
"Create your admin account": "Opret din administratorkonto",
"Repeat Password": "Gentag adgangskoden",
"Resource Record Type": "Resource Record Type",
respTime: "Resp. Time (ms)",
notAvailableShort: "N/A",
Create: "Create",
clearEventsMsg: "Are you sure want to delete all events for this monitor?",
clearHeartbeatsMsg: "Are you sure want to delete all heartbeats for this monitor?",
confirmClearStatisticsMsg: "Are you sure want to delete ALL statistics?",
"Clear Data": "Clear Data",
Events: "Events",
Heartbeats: "Heartbeats",
"Auto Get": "Auto Get",
enableDefaultNotificationDescription: "For every new monitor this notification will be enabled by default. You can still disable the notification separately for each monitor.",
"Default enabled": "Default enabled",
"Also apply to existing monitors": "Also apply to existing monitors",
"Import/Export Backup": "Import/Export Backup",
Export: "Export",
Import: "Import",
backupDescription: "You can backup all monitors and all notifications into a JSON file.",
backupDescription2: "PS: History and event data is not included.",
backupDescription3: "Sensitive data such as notification tokens is included in the export file, please keep it carefully.",
alertNoFile: "Please select a file to import.",
alertWrongFileType: "Please select a JSON file."
}

View File

@@ -70,7 +70,7 @@ export default {
Timezone: "Zeitzone",
"Search Engine Visibility": "Suchmaschinensichtbarkeit",
"Allow indexing": "Indizierung zulassen",
"Discourage search engines from indexing site": "Halte Suchmaschinen von der Indexierung der Site ab",
"Discourage search engines from indexing site": "Halte Suchmaschinen von der Indexierung der Seite ab",
"Change Password": "Passwort ändern",
"Current Password": "Dezeitiges Passwort",
"New Password": "Neues Passwort",
@@ -93,15 +93,40 @@ export default {
"No Monitors, please": "Keine Monitore, bitte",
"add one": "hinzufügen",
"Notification Type": "Benachrichtigungs Dienst",
"Email": "E-Mail",
"Test": "Test",
Email: "E-Mail",
Test: "Test",
"Certificate Info": "Zertifikatsinfo",
keywordDescription: "Suche nach einen Schlüsselwort in einer schlichten HTML oder JSON Ausgabe. Bitte beachte, es wird in der Groß-/Kleinschreibung unterschieden.",
keywordDescription: "Suche nach einem Schlüsselwort in der HTML oder JSON Ausgabe. Bitte beachte, es wird in der Groß-/Kleinschreibung unterschieden.",
deleteMonitorMsg: "Bist du sicher das du den Monitor löschen möchtest?",
deleteNotificationMsg: "Möchtest du diese Benachrichtigung wirklich für alle Monitore löschen?",
resoverserverDescription: "Cloudflare ist der Standardserver, dieser kann jederzeit geändern werden.",
resoverserverDescription: "Cloudflare ist als der Standardserver festgelegt, dieser kann jederzeit geändern werden.",
"Resolver Server": "Auflösungsserver",
rrtypeDescription: "Wähle den RR-Typ aus, welchen du überwachen möchtest.",
"Last Result": "Letztes Ergebnis",
pauseMonitorMsg: "Bist du sicher das du den Monitor pausieren möchtest?",
clearEventsMsg: "Bist du sicher das du alle Ereignisse für diesen Monitor löschen möchtest?",
clearHeartbeatsMsg: "Bist du sicher das du alle Statistiken für diesen Monitor löschen möchtest?",
"Clear Data": "Lösche Daten",
Events: "Ereignisse",
Heartbeats: "Statistiken",
confirmClearStatisticsMsg: "Bist du sicher das du ALLE Statistiken löschen möchtest?",
"Create your admin account": "Erstelle dein Admin Konto",
"Repeat Password": "Wiederhole das Passwort",
"Resource Record Type": "Resource Record Type",
"Import/Export Backup": "Import/Export Backup",
"Export": "Export",
"Import": "Import",
respTime: "Antw. Zeit (ms)",
notAvailableShort: "N/A",
"Default enabled": "Standardmäßig aktiviert",
"Also apply to existing monitors": "Auch für alle existierenden Monitore aktivieren",
enableDefaultNotificationDescription: "Für jeden neuen Monitor wird diese Benachrichtigung standardmäßig aktiviert. Die Benachrichtigung kann weiterhin für jeden Monitor separat deaktiviert werden.",
Create: "Erstellen",
"Auto Get": "Auto Get",
backupDescription: "Es können alle Monitore und Benachrichtigungen in einer JSON-Datei gesichert werden.",
backupDescription2: "PS: Verlaufs- und Ereignisdaten sind nicht enthalten.",
backupDescription3: "Sensible Daten wie Benachrichtigungstoken sind in der Exportdatei enthalten, bitte bewahre sie sorgfältig auf.",
alertNoFile: "Bitte wähle eine Datei zum importieren aus.",
alertWrongFileType: "Bitte wähle eine JSON Datei aus.",
"Clear all statistics": "Lösche alle Statistiken"
}

View File

@@ -16,4 +16,117 @@ export default {
resoverserverDescription: "Cloudflare is the default server, you can change the resolver server anytime.",
rrtypeDescription: "Select the RR-Type you want to monitor",
pauseMonitorMsg: "Are you sure want to pause?",
enableDefaultNotificationDescription: "For every new monitor this notification will be enabled by default. You can still disable the notification separately for each monitor.",
clearEventsMsg: "Are you sure want to delete all events for this monitor?",
clearHeartbeatsMsg: "Are you sure want to delete all heartbeats for this monitor?",
confirmClearStatisticsMsg: "Are you sure want to delete ALL statistics?",
Settings: "Settings",
Dashboard: "Dashboard",
"New Update": "New Update",
Language: "Language",
Appearance: "Appearance",
Theme: "Theme",
General: "General",
Version: "Version",
"Check Update On GitHub": "Check Update On GitHub",
List: "List",
Add: "Add",
"Add New Monitor": "Add New Monitor",
"Quick Stats": "Quick Stats",
Up: "Up",
Down: "Down",
Pending: "Pending",
Unknown: "Unknown",
Pause: "Pause",
Name: "Name",
Status: "Status",
DateTime: "DateTime",
Message: "Message",
"No important events": "No important events",
Resume: "Resume",
Edit: "Edit",
Delete: "Delete",
Current: "Current",
Uptime: "Uptime",
"Cert Exp.": "Cert Exp.",
days: "days",
day: "day",
"-day": "-day",
hour: "hour",
"-hour": "-hour",
Response: "Response",
Ping: "Ping",
"Monitor Type": "Monitor Type",
Keyword: "Keyword",
"Friendly Name": "Friendly Name",
URL: "URL",
Hostname: "Hostname",
Port: "Port",
"Heartbeat Interval": "Heartbeat Interval",
Retries: "Retries",
Advanced: "Advanced",
"Upside Down Mode": "Upside Down Mode",
"Max. Redirects": "Max. Redirects",
"Accepted Status Codes": "Accepted Status Codes",
Save: "Save",
Notifications: "Notifications",
"Not available, please setup.": "Not available, please setup.",
"Setup Notification": "Setup Notification",
Light: "Light",
Dark: "Dark",
Auto: "Auto",
"Theme - Heartbeat Bar": "Theme - Heartbeat Bar",
Normal: "Normal",
Bottom: "Bottom",
None: "None",
Timezone: "Timezone",
"Search Engine Visibility": "Search Engine Visibility",
"Allow indexing": "Allow indexing",
"Discourage search engines from indexing site": "Discourage search engines from indexing site",
"Change Password": "Change Password",
"Current Password": "Current Password",
"New Password": "New Password",
"Repeat New Password": "Repeat New Password",
"Update Password": "Update Password",
"Disable Auth": "Disable Auth",
"Enable Auth": "Enable Auth",
Logout: "Logout",
Leave: "Leave",
"I understand, please disable": "I understand, please disable",
Confirm: "Confirm",
Yes: "Yes",
No: "No",
Username: "Username",
Password: "Password",
"Remember me": "Remember me",
Login: "Login",
"No Monitors, please": "No Monitors, please",
"add one": "add one",
"Notification Type": "Notification Type",
Email: "Email",
Test: "Test",
"Certificate Info": "Certificate Info",
"Resolver Server": "Resolver Server",
"Resource Record Type": "Resource Record Type",
"Last Result": "Last Result",
"Create your admin account": "Create your admin account",
"Repeat Password": "Repeat Password",
"Import/Export Backup": "Import/Export Backup",
Export: "Export",
Import: "Import",
respTime: "Resp. Time (ms)",
notAvailableShort: "N/A",
"Default enabled": "Default enabled",
"Also apply to existing monitors": "Also apply to existing monitors",
Create: "Create",
"Clear Data": "Clear Data",
Events: "Events",
Heartbeats: "Heartbeats",
"Auto Get": "Auto Get",
backupDescription: "You can backup all monitors and all notifications into a JSON file.",
backupDescription2: "PS: History and event data is not included.",
backupDescription3: "Sensitive data such as notification tokens is included in the export file, please keep it carefully.",
alertNoFile: "Please select a file to import.",
alertWrongFileType: "Please select a JSON file.",
"Clear all statistics": "Clear all Statistics"
}

131
src/languages/es-ES.js Normal file
View File

@@ -0,0 +1,131 @@
export default {
languageName: "Español",
checkEverySecond: "Comprobar cada {0} segundos.",
"Avg.": "Media. ",
retriesDescription: "Número máximo de intentos antes de que el servicio se marque como CAÍDO y una notificación sea enviada.",
ignoreTLSError: "Ignorar error TLS/SSL para sitios web HTTPS",
upsideDownModeDescription: "Invertir el estado. Si el servicio es alcanzable, está CAÍDO.",
maxRedirectDescription: "Número máximo de direcciones a seguir. Establecer a 0 para deshabilitar.",
acceptedStatusCodesDescription: "Seleccionar los códigos de estado que se consideran como respuesta exitosa.",
passwordNotMatchMsg: "La contraseña repetida no coincide.",
notificationDescription: "Por favor asigne una notificación a el/los monitor(es) para hacerlos funcional(es).",
keywordDescription: "Palabra clave en HTML plano o respuesta JSON y es sensible a mayúsculas",
pauseDashboardHome: "Pausar",
deleteMonitorMsg: "¿Seguro que quieres eliminar este monitor?",
deleteNotificationMsg: "¿Seguro que quieres eliminar esta notificación para todos los monitores?",
resoverserverDescription: "Cloudflare es el servidor por defecto, puedes cambiar el servidor de resolución en cualquier momento.",
rrtypeDescription: "Selecciona el tipo de registro que quieres monitorizar",
pauseMonitorMsg: "¿Seguro que quieres pausar?",
Settings: "Ajustes",
Dashboard: "Panel",
"New Update": "Vueva actualización",
Language: "Idioma",
Appearance: "Apariencia",
Theme: "Tema",
General: "General",
Version: "Versión",
"Check Update On GitHub": "Comprobar actualizaciones en GitHub",
List: "Lista",
Add: "Añadir",
"Add New Monitor": "Añadir nuevo monitor",
"Quick Stats": "Estadísticas rápidas",
Up: "Funcional",
Down: "Caído",
Pending: "Pendiente",
Unknown: "Desconociso",
Pause: "Pausa",
Name: "Nombre",
Status: "Estado",
DateTime: "Fecha y Hora",
Message: "Mensaje",
"No important events": "No hay eventos importantes",
Resume: "Reanudar",
Edit: "Editar",
Delete: "Eliminar",
Current: "Actual",
Uptime: "Tiempo activo",
"Cert Exp.": "Caducidad cert.",
days: "días",
day: "día",
"-day": "-día",
hour: "hora",
"-hour": "-hora",
Response: "Respuesta",
Ping: "Ping",
"Monitor Type": "Tipo de Monitor",
Keyword: "Palabra clave",
"Friendly Name": "Nombre sencillo",
URL: "URL",
Hostname: "Nombre del host",
Port: "Puerto",
"Heartbeat Interval": "Intervalo de latido",
Retries: "Reintentos",
Advanced: "Avanzado",
"Upside Down Mode": "Modo invertido",
"Max. Redirects": "Máx. redirecciones",
"Accepted Status Codes": "Códigos de estado aceptados",
Save: "Guardar",
Notifications: "Notificaciones",
"Not available, please setup.": "No disponible, por favor configurar.",
"Setup Notification": "Configurar notificación",
Light: "Claro",
Dark: "Oscuro",
Auto: "Auto",
"Theme - Heartbeat Bar": "Tema - Barra de intervalo de latido",
Normal: "Normal",
Bottom: "Abajo",
None: "Ninguno",
Timezone: "Zona horaria",
"Search Engine Visibility": "Visibilidad motor de búsqueda",
"Allow indexing": "Permitir indexación",
"Discourage search engines from indexing site": "Disuadir a los motores de búsqueda de indexar el sitio",
"Change Password": "Cambiar contraseña",
"Current Password": "Contraseña actual",
"New Password": "Nueva contraseña",
"Repeat New Password": "Repetir nueva contraseña",
"Update Password": "Actualizar contraseña",
"Disable Auth": "Deshabilitar Autenticación ",
"Enable Auth": "Habilitar Autenticación ",
Logout: "Cerrar sesión",
Leave: "Salir",
"I understand, please disable": "Lo comprendo, por favor deshabilitar",
Confirm: "Confirmar",
Yes: "Sí",
No: "No",
Username: "Usuario",
Password: "Contraseña",
"Remember me": "Recordarme",
Login: "Acceso",
"No Monitors, please": "Sin monitores, por favor",
"add one": "añade uno",
"Notification Type": "Tipo de notificación",
Email: "Email",
Test: "Test",
"Certificate Info": "Información del certificado ",
"Resolver Server": "Servidor de resolución",
"Resource Record Type": "Tipo de Registro",
"Last Result": "Último resultado",
"Create your admin account": "Crea tu cuenta de administrador",
"Repeat Password": "Repetir contraseña",
respTime: "Tiempo de resp. (ms)",
notAvailableShort: "N/A",
Create: "Create",
clearEventsMsg: "Are you sure want to delete all events for this monitor?",
clearHeartbeatsMsg: "Are you sure want to delete all heartbeats for this monitor?",
confirmClearStatisticsMsg: "Are you sure want to delete ALL statistics?",
"Clear Data": "Clear Data",
Events: "Events",
Heartbeats: "Heartbeats",
"Auto Get": "Auto Get",
enableDefaultNotificationDescription: "For every new monitor this notification will be enabled by default. You can still disable the notification separately for each monitor.",
"Default enabled": "Default enabled",
"Also apply to existing monitors": "Also apply to existing monitors",
"Import/Export Backup": "Import/Export Backup",
Export: "Export",
Import: "Import",
backupDescription: "You can backup all monitors and all notifications into a JSON file.",
backupDescription2: "PS: History and event data is not included.",
backupDescription3: "Sensitive data such as notification tokens is included in the export file, please keep it carefully.",
alertNoFile: "Please select a file to import.",
alertWrongFileType: "Please select a JSON file."
}

131
src/languages/et-EE.js Normal file
View File

@@ -0,0 +1,131 @@
export default {
languageName: "eesti",
checkEverySecond: "Kontrolli {0} sekundilise vahega.",
"Avg.": "≈ ",
retriesDescription: "Mitu korda tuleb kontrollida, mille järel märkida 'maas' ja saata välja teavitus.",
ignoreTLSError: "Eira TLS/SSL viga HTTPS veebisaitidel.",
upsideDownModeDescription: "Käitle teenuse saadavust rikkena, teenuse kättesaamatust töötavaks.",
maxRedirectDescription: "Suurim arv ümbersuunamisi, millele järgida. 0 ei luba ühtegi ",
acceptedStatusCodesDescription: "Vali välja HTTP koodid, mida arvestada kõlblikuks.",
passwordNotMatchMsg: "Salasõnad ei kattu.",
notificationDescription: "Teavitusmeetodi kasutamiseks seo see seirega.",
keywordDescription: "Jälgi võtmesõna HTML või JSON vastustes. (tõstutundlik)",
pauseDashboardHome: "Seiskamine",
deleteMonitorMsg: "Kas soovid eemaldada seire?",
deleteNotificationMsg: "Kas soovid eemaldada selle teavitusmeetodi kõikidelt seiretelt?",
resoverserverDescription: "Cloudflare on vaikimisi pöördserver.",
rrtypeDescription: "Vali kirje tüüp, mida soovid jälgida.",
pauseMonitorMsg: "Kas soovid peatada seire?",
Settings: "Seaded",
Dashboard: "Töölaud",
"New Update": "Uuem tarkvara versioon on saadaval.",
Language: "Keel",
Appearance: "Välimus",
Theme: "Teema",
General: "Üldine",
Version: "Versioon",
"Check Update On GitHub": "Otsi uuendusi GitHub'ist",
List: "Nimekiri",
Add: "Lisa",
"Add New Monitor": "Seire lisamine",
"Quick Stats": "Ülevaade",
Up: "Töökorras",
Down: "Rikkis",
Pending: "Määramisel",
Unknown: "Teadmata",
Pause: "Seiskamine",
Name: "Nimi",
Status: "Olek",
DateTime: "Kuupäev",
Message: "Tulemus",
"No important events": "Märkimisväärsed juhtumid puuduvad.",
Resume: "Taasta",
Edit: "Muutmine",
Delete: "Eemalda",
Current: "Hetkeseisund",
Uptime: "Eluiga",
"Cert Exp.": "Sert. aegumine",
days: "päeva",
day: "päev",
"-day": "-päev",
hour: "tund",
"-hour": "-tund",
Response: "Vastus",
Ping: "Ping",
"Monitor Type": "Seire tüüp",
Keyword: "Võtmesõna",
"Friendly Name": "Sõbralik nimi",
URL: "URL",
Hostname: "Hostname",
Port: "Port",
"Heartbeat Interval": "Tukse sagedus",
Retries: "Korduskatsed",
Advanced: "Rohkem",
"Upside Down Mode": "Tagurpidi seire",
"Max. Redirects": "Max. ümbersuunamine",
"Accepted Status Codes": "Kõlblikud HTTP koodid",
Save: "Salvesta",
Notifications: "Teavitused",
"Not available, please setup.": "Ühtegi teavitusteenust pole saadaval.",
"Setup Notification": "Lisa teavitusteenus",
Light: "hele",
Dark: "tume",
Auto: "automaatne",
"Theme - Heartbeat Bar": "Teemasäte — tuksete riba",
Normal: "tavaline",
Bottom: "all",
None: "puudub",
Timezone: "Ajatsoon",
"Search Engine Visibility": "Otsimootorite ligipääs",
"Allow indexing": "Luba indekseerimine",
"Discourage search engines from indexing site": "Keela selle saidi indekseerimine otsimootorite poolt",
"Change Password": "Muuda parooli",
"Current Password": "praegune parool",
"New Password": "uus parool",
"Repeat New Password": "korda salasõna",
"Update Password": "Uuenda salasõna",
"Disable Auth": "Lülita autentimine välja",
"Enable Auth": "Lülita autentimine sisse",
Logout: "Logi välja",
Leave: "Lahku",
"I understand, please disable": "Olen tutvunud riskidega, lülita välja",
Confirm: "Kinnita",
Yes: "Jah",
No: "Ei",
Username: "kasutajanimi",
Password: "parool",
"Remember me": "Mäleta mind",
Login: "Logi sisse",
"No Monitors, please": "Seired puuduvad.",
"add one": "Lisa esimene",
"Notification Type": "Teavituse tüüp",
Email: "e-posti aadress",
Test: "Saada prooviteavitus",
"Certificate Info": "Sertifikaadi teave",
"Resolver Server": "Server, mis vastab DNS päringutele.",
"Resource Record Type": "DNS kirje tüüp",
"Last Result": "Viimane",
"Create your admin account": "Admininstraatori konto loomine",
"Repeat Password": "korda salasõna",
respTime: "Reageerimisaeg (ms)",
notAvailableShort: "N/A",
enableDefaultNotificationDescription: "For every new monitor this notification will be enabled by default. You can still disable the notification separately for each monitor.",
clearEventsMsg: "Are you sure want to delete all events for this monitor?",
clearHeartbeatsMsg: "Are you sure want to delete all heartbeats for this monitor?",
confirmClearStatisticsMsg: "Are you sure want to delete ALL statistics?",
"Import/Export Backup": "Import/Export Backup",
Export: "Export",
Import: "Import",
"Default enabled": "Default enabled",
"Also apply to existing monitors": "Also apply to existing monitors",
Create: "Create",
"Clear Data": "Clear Data",
Events: "Events",
Heartbeats: "Heartbeats",
"Auto Get": "Auto Get",
backupDescription: "You can backup all monitors and all notifications into a JSON file.",
backupDescription2: "PS: History and event data is not included.",
backupDescription3: "Sensitive data such as notification tokens is included in the export file, please keep it carefully.",
alertNoFile: "Please select a file to import.",
alertWrongFileType: "Please select a JSON file."
}

View File

@@ -1,7 +1,7 @@
export default {
languageName: "Français (France)",
Settings: "Paramètres",
Dashboard: "Dashboard",
Dashboard: "Tableau de bord",
"New Update": "Mise à jour disponible",
Language: "Langue",
Appearance: "Apparence",
@@ -11,11 +11,11 @@ export default {
"Check Update On GitHub": "Consulter les mises à jour sur Github",
List: "Lister",
Add: "Ajouter",
"Add New Monitor": "Ajouter un nouveau check",
"Add New Monitor": "Ajouter une nouvelle sonde",
"Quick Stats": "Résumé",
Up: "En ligne",
Down: "Hors ligne",
Pending: "Dans la file d'attente",
Pending: "En attente",
Unknown: "Inconnu",
Pause: "En Pause",
pauseDashboardHome: "Éléments mis en pause",
@@ -29,60 +29,60 @@ export default {
Delete: "Supprimer",
Current: "Actuellement",
Uptime: "Uptime",
"Cert Exp.": "Cert Exp.",
"Cert Exp.": "Certificat expiré",
days: "Jours",
day: "Jour",
"-day": "Demi-Journée",
"-day": "Journée",
hour: "Heure",
"-hour": "Demi-Heure",
"-hour": "Heures",
checkEverySecond: "Vérifier toutes les {0} secondes",
"Avg.": "Moy.",
Response: "Réponse",
"Avg.": "Moyen",
Response: "Temps de réponse",
Ping: "Ping",
"Monitor Type": "Type de Monitoring",
"Monitor Type": "Type de Sonde",
Keyword: "Mot-clé",
"Friendly Name": "Nom d'affichage",
URL: "URL",
Hostname: "Nom d'hôte",
Port: "Port",
"Heartbeat Interval": "Intervale de vérifications",
"Heartbeat Interval": "Intervale de vérification",
Retries: "Essais",
retriesDescription: "Nombre d'essais avant que le service soit déclaré hors-ligne.",
Advanced: "Avancé",
ignoreTLSError: "Ignorer les erreurs liées au certificat SSL/TLS",
"Upside Down Mode": "Mode inversé",
upsideDownModeDescription: "Si le service est en ligne il sera alors noté hors-ligne et vice-versa.",
"Max. Redirects": "Redirections",
upsideDownModeDescription: "Si le service est en ligne, il sera alors noté hors-ligne et vice-versa.",
"Max. Redirects": "Nombre maximum de redirections",
maxRedirectDescription: "Nombre maximal de redirections avant que le service soit noté hors-ligne.",
"Accepted Status Codes": "Codes HTTP",
acceptedStatusCodesDescription: "Si les codes HTTP reçus sont ceux séléctionnés, alors le serveur sera noté en ligne.",
acceptedStatusCodesDescription: "Codes HTTP considérés comme en ligne",
Save: "Sauvegarder",
Notifications: "Notifications",
"Not available, please setup.": "Créez des notifications depuis les paramètres.",
"Not available, please setup.": "Pas de système de notification disponible, merci de le configurer",
"Setup Notification": "Créer une notification",
Light: "Clair",
Dark: "Sombre",
Auto: "Automatique",
"Theme - Heartbeat Bar": "Voir les services monitorés",
"Theme - Heartbeat Bar": "Voir les services surveillés",
Normal: "Général",
Bottom: "Au dessus",
None: "Neutre",
Bottom: "En dessous",
None: "Rien",
Timezone: "Fuseau Horaire",
"Search Engine Visibility": "SEO",
"Search Engine Visibility": "Visibilité par les moteurs de recherche",
"Allow indexing": "Autoriser l'indexation par des moteurs de recherche",
"Discourage search engines from indexing site": "Empêche les moteurs de recherche d'indexer votre site",
"Discourage search engines from indexing site": "Refuser l'indexation par des moteurs de recherche",
"Change Password": "Changer le mot de passe",
"Current Password": "Mot de passe actuel",
"New Password": "Nouveau mot de passe",
"Repeat New Password": "Répéter votre nouveau mot de passe",
passwordNotMatchMsg: "Les mots de passe ne correspondent pas",
"Update Password": "Mettre à jour le mot de passe",
"Disable Auth": "Désactiver l'authentification intégrée",
"Disable Auth": "Désactiver l'authentification",
"Enable Auth": "Activer l'authentification",
Logout: "Se déconnecter",
notificationDescription: "Une fois ajoutée, vous devez l'activer manuellement dans les paramètres de vos hosts.",
notificationDescription: "Une fois ajoutée, vous devez l'activer manuellement dans les paramètres de vos hôtes.",
Leave: "Quitter",
"I understand, please disable": "Je comprends, je l'ai désactivé",
"I understand, please disable": "J'ai compris, désactivez-le",
Confirm: "Confirmer",
Yes: "Oui",
No: "Non",
@@ -90,17 +90,42 @@ export default {
Password: "Mot de passe",
"Remember me": "Se souvenir de moi",
Login: "Se connecter",
"No Monitors, please": "Pas de monitor, veuillez ",
"add one": "en ajouter un.",
"No Monitors, please": "Pas de sondes, veuillez ",
"add one": "en ajouter une.",
"Notification Type": "Type de notification",
"Email": "Email",
"Test": "Tester",
keywordDescription: "Le mot clé sera cherché dans la réponse HTML/JSON reçue du site internet.",
"Certificate Info": "Des informations sur le certificat SSL",
deleteMonitorMsg: "Êtes-vous sûr de vouloir supprimer ce monitor ?",
Email: "Email",
Test: "Tester",
keywordDescription: "Le mot clé sera recherché dans la réponse HTML/JSON reçue du site internet.",
"Certificate Info": "Informations sur le certificat SSL",
deleteMonitorMsg: "Êtes-vous sûr de vouloir supprimer cette sonde ?",
deleteNotificationMsg: "Êtes-vous sûr de vouloir supprimer ce type de notifications ? Une fois désactivée, les services qui l'utilisent ne pourront plus envoyer de notifications.",
"Resolver Server": "Serveur DNS utilisé",
"Resource Record Type": "Type d'enregistrement DNS recherché",
resoverserverDescription: "Le DNS de cloudflare est utilisé par défaut, mais vous pouvez le changer si vous le souhaitez.",
rrtypeDescription: "Veuillez séléctionner un type d'enregistrement DNS",
pauseMonitorMsg: "Etes vous sur de vouloir mettre en pause cette sonde ?",
"Last Result": "Dernier résultat",
"Create your admin account": "Créez votre compte administrateur",
"Repeat Password": "Répéter le mot de passe",
respTime: "Temps de réponse (ms)",
notAvailableShort: "N/A",
Create: "Créer",
clearEventsMsg: "Are you sure want to delete all events for this monitor?",
clearHeartbeatsMsg: "Are you sure want to delete all heartbeats for this monitor?",
confirmClearStatisticsMsg: "Are you sure want to delete ALL statistics?",
"Clear Data": "Clear Data",
Events: "Events",
Heartbeats: "Heartbeats",
"Auto Get": "Auto Get",
enableDefaultNotificationDescription: "For every new monitor this notification will be enabled by default. You can still disable the notification separately for each monitor.",
"Default enabled": "Default enabled",
"Also apply to existing monitors": "Also apply to existing monitors",
"Import/Export Backup": "Import/Export Backup",
Export: "Export",
Import: "Import",
backupDescription: "You can backup all monitors and all notifications into a JSON file.",
backupDescription2: "PS: History and event data is not included.",
backupDescription3: "Sensitive data such as notification tokens is included in the export file, please keep it carefully.",
alertNoFile: "Please select a file to import.",
alertWrongFileType: "Please select a JSON file."
}

131
src/languages/it-IT.js Normal file
View File

@@ -0,0 +1,131 @@
export default {
languageName: "Italiano (Italian)",
checkEverySecond: "controlla ogni {0} secondi",
"Avg.": "Media",
retriesDescription: "Tentativi da fare prima che il servizio venga marcato come \"giù\" e che una notifica venga inviata.",
ignoreTLSError: "Ignora gli errori TLS/SSL per i siti in HTTPS.",
upsideDownModeDescription: "Capovolgi lo stato. Se il servizio è raggiungibile viene marcato come \"GIÙ\".",
maxRedirectDescription: "Numero massimo di redirezionamenti consentito. Per disabilitare impostare \"0\".",
acceptedStatusCodesDescription: "Inserire i codici di stato considerati come risposte corrette.",
passwordNotMatchMsg: "La password non coincide.",
notificationDescription: "Assegnare la notifica a uno o più elementi monitorati per metterla in funzione.",
keywordDescription: "Cerca la parola chiave nella risposta in html o JSON e fai distinzione tra maiuscole e minuscole",
pauseDashboardHome: "In Pausa",
deleteMonitorMsg: "Si è certi di voler eliminare questo monitoraggio?",
deleteNotificationMsg: "Si è certi di voler eliminare questa notifica per tutti gli oggetti monitorati?",
resoverserverDescription: "Cloudflare è il server predefinito, è possibile cambiare il server DNS.",
rrtypeDescription: "Scegliere il tipo di RR che si vuole monitorare",
pauseMonitorMsg: "Si è certi di voler mettere in pausa?",
clearEventsMsg: "Si è certi di voler eliminare tutti gli eventi per questo servizio?",
clearHeartbeatsMsg: "Si è certi di voler eliminare tutti gli intervalli di controllo per questo servizio?",
confirmClearStatisticsMsg: "Si è certi di voler eliminare TUTTE le statistiche?",
Settings: "Impostazioni",
Dashboard: "Cruscotto",
"New Update": "Nuovo Aggiornamento Disponibile",
Language: "Lingua",
Appearance: "Aspetto",
Theme: "Tema",
General: "Generali",
Version: "Versione",
"Check Update On GitHub": "Controlla aggiornamenti su GitHub",
List: "Lista",
Add: "Aggiungi",
"Add New Monitor": "Aggiungi un nuovo monitoraggio",
"Quick Stats": "Statistiche rapide",
Up: "Su",
Down: "Giù",
Pending: "Pendente",
Unknown: "Sconosciuti",
Pause: "Metti in Pausa",
Name: "Nome",
Status: "Stato",
DateTime: "Data e Ora",
Message: "Messaggio",
"No important events": "Nessun evento importante",
Resume: "Riprendi",
Edit: "Modifica",
Delete: "Elimina",
Current: "Corrente",
Uptime: "Tempo di attività",
"Cert Exp.": "Scadenza certificato",
days: "giorni",
day: "giorno",
"-day": "-giorni",
hour: "ora",
"-hour": "-ore",
Response: "Risposta",
Ping: "Ping",
"Monitor Type": "Tipo di Monitoraggio",
Keyword: "Parola chiave",
"Friendly Name": "Nome Amichevole",
URL: "URL",
Hostname: "Nome Host",
Port: "Porta",
"Heartbeat Interval": "Intervallo di controllo",
Retries: "Tentativi",
Advanced: "Avanzate",
"Upside Down Mode": "Modalità capovolta",
"Max. Redirects": "Redirezionamenti massimi",
"Accepted Status Codes": "Codici di stato accettati",
Save: "Salva",
Notifications: "Notifiche",
"Not available, please setup.": "Non disponibili, da impostare.",
"Setup Notification": "Imposta le notifiche",
Light: "Chiaro",
Dark: "Scuro",
Auto: "Automatico",
"Theme - Heartbeat Bar": "Tema - Barra di Stato",
Normal: "Normale",
Bottom: "Sotto",
None: "Nessuna",
Timezone: "Fuso Orario",
"Search Engine Visibility": "Visibilità ai motori di ricerca",
"Allow indexing": "Permetti l'indicizzazione",
"Discourage search engines from indexing site": "Scoraggia l'indicizzazione da parte dei motori di ricerca",
"Change Password": "Cambio Password",
"Current Password": "Password Corrente",
"New Password": "Nuova Password",
"Repeat New Password": "Ripetere la nuova Password",
"Update Password": "Modifica Password",
"Disable Auth": "Disabilita l'autenticazione",
"Enable Auth": "Abilita Autenticazione",
Logout: "Esci",
Leave: "Annulla",
"I understand, please disable": "Lo capisco, disabilitare l'autenticazione.",
Confirm: "Conferma",
Yes: "Sì",
No: "No",
Username: "Nome Utente",
Password: "Password",
"Remember me": "Ricordami",
Login: "Accesso",
"No Monitors, please": "Nessun monitoraggio, cortesemente",
"add one": "aggiungerne uno",
"Notification Type": "Tipo di notifica",
Email: "E-mail",
Test: "Prova",
"Certificate Info": "Informazioni sul certificato",
"Resolver Server": "Server DNS",
"Resource Record Type": "Tipo di Resource Record",
"Last Result": "Ultimo risultato",
"Create your admin account": "Crea l'account amministratore",
"Repeat Password": "Ripeti Password",
respTime: "Tempo di Risposta (ms)",
notAvailableShort: "N/D",
Create: "Crea",
"Clear Data": "Cancella dati",
Events: "Eventi",
Heartbeats: "Controlli",
"Auto Get": "Auto Get",
enableDefaultNotificationDescription: "For every new monitor this notification will be enabled by default. You can still disable the notification separately for each monitor.",
"Import/Export Backup": "Import/Export Backup",
Export: "Export",
Import: "Import",
"Default enabled": "Default enabled",
"Also apply to existing monitors": "Also apply to existing monitors",
backupDescription: "You can backup all monitors and all notifications into a JSON file.",
backupDescription2: "PS: History and event data is not included.",
backupDescription3: "Sensitive data such as notification tokens is included in the export file, please keep it carefully.",
alertNoFile: "Please select a file to import.",
alertWrongFileType: "Please select a JSON file."
}

131
src/languages/ja.js Normal file
View File

@@ -0,0 +1,131 @@
export default {
languageName: "日本語",
checkEverySecond: "{0}秒ごとにチェックします。",
"Avg.": "平均 ",
retriesDescription: "サービスがダウンとしてマークされ、通知が送信されるまでの最大リトライ数",
ignoreTLSError: "HTTPS ウェブサイトの TLS/SSL エラーを無視する",
upsideDownModeDescription: "ステータスの扱いを逆にします。サービスに到達可能な場合は、DOWNとなる。",
maxRedirectDescription: "フォローするリダイレクトの最大数。リダイレクトを無効にするには0を設定する。",
acceptedStatusCodesDescription: "成功した応答とみなされるステータスコードを選択する。",
passwordNotMatchMsg: "繰り返しのパスワードが一致しません。",
notificationDescription: "監視を機能させるには、監視に通知を割り当ててください。",
keywordDescription: "プレーンHTMLまたはJSON応答でキーワードを検索し、大文字と小文字を区別します",
pauseDashboardHome: "一時停止",
deleteMonitorMsg: "この監視を削除してよろしいですか?",
deleteNotificationMsg: "全ての監視のこの通知を削除してよろしいですか?",
resoverserverDescription: "Cloudflareがデフォルトのサーバーですが、いつでもリゾルバサーバーを変更できます。",
rrtypeDescription: "監視するRRタイプを選択します",
pauseMonitorMsg: "一時停止しますか?",
Settings: "設定",
Dashboard: "ダッシュボード",
"New Update": "New Update",
Language: "言語",
Appearance: "外観",
Theme: "テーマ",
General: "General",
Version: "バージョン",
"Check Update On GitHub": "GitHubでアップデートを確認する",
List: "一覧",
Add: "追加",
"Add New Monitor": "監視の追加",
"Quick Stats": "統計",
Up: "Up",
Down: "Down",
Pending: "中止",
Unknown: "不明",
Pause: "一時停止",
Name: "名前",
Status: "ステータス",
DateTime: "日時",
Message: "メッセージ",
"No important events": "重要なイベントなし",
Resume: "再開",
Edit: "編集",
Delete: "削除",
Current: "現在",
Uptime: "起動時間",
"Cert Exp.": "証明書有効期限",
days: "日間",
day: "日",
"-day": "-日",
hour: "時間",
"-hour": "-時間",
Response: "レスポンス",
Ping: "Ping",
"Monitor Type": "監視タイプ",
Keyword: "キーワード",
"Friendly Name": "Friendly Name",
URL: "URL",
Hostname: "ホスト名",
Port: "ポート",
"Heartbeat Interval": "監視間隔",
Retries: "Retries",
Advanced: "Advanced",
"Upside Down Mode": "Upside Down Mode",
"Max. Redirects": "最大リダイレクト数",
"Accepted Status Codes": "承認されたステータスコード",
Save: "保存",
Notifications: "通知",
"Not available, please setup.": "利用できません。設定してください。",
"Setup Notification": "通知設定",
Light: "Light",
Dark: "Dark",
Auto: "Auto",
"Theme - Heartbeat Bar": "Theme - Heartbeat Bar",
Normal: "通常",
Bottom: "下部",
None: "なし",
Timezone: "タイムゾーン",
"Search Engine Visibility": "検索エンジンでの表示",
"Allow indexing": "インデックス作成を許可する",
"Discourage search engines from indexing site": "検索エンジンにインデックスさせないようにする",
"Change Password": "パスワード変更",
"Current Password": "現在のパスワード",
"New Password": "新しいパスワード",
"Repeat New Password": "確認のため新しいパスワードをもう一度",
"Update Password": "パスワードの更新",
"Disable Auth": "認証の無効化",
"Enable Auth": "認証の有効化",
Logout: "ログアウト",
Leave: "作業を中止する",
"I understand, please disable": "理解した上で無効化する",
Confirm: "確認",
Yes: "はい",
No: "いいえ",
Username: "ユーザー名",
Password: "パスワード",
"Remember me": "パスワードを忘れた場合",
Login: "ログイン",
"No Monitors, please": "監視がありません",
"add one": "add one",
"Notification Type": "通知タイプ",
Email: "Eメール",
Test: "テスト",
"Certificate Info": "証明書情報",
"Resolver Server": "問い合わせ先DNSサーバ",
"Resource Record Type": "DNSレコード設定",
"Last Result": "最終結果",
"Create your admin account": "Create your admin account",
"Repeat Password": "Repeat Password",
respTime: "Resp. Time (ms)",
notAvailableShort: "N/A",
Create: "Create",
clearEventsMsg: "Are you sure want to delete all events for this monitor?",
clearHeartbeatsMsg: "Are you sure want to delete all heartbeats for this monitor?",
confirmClearStatisticsMsg: "Are you sure want to delete ALL statistics?",
"Clear Data": "Clear Data",
Events: "Events",
Heartbeats: "Heartbeats",
"Auto Get": "Auto Get",
enableDefaultNotificationDescription: "For every new monitor this notification will be enabled by default. You can still disable the notification separately for each monitor.",
"Default enabled": "Default enabled",
"Also apply to existing monitors": "Also apply to existing monitors",
"Import/Export Backup": "Import/Export Backup",
Export: "Export",
Import: "Import",
backupDescription: "You can backup all monitors and all notifications into a JSON file.",
backupDescription2: "PS: History and event data is not included.",
backupDescription3: "Sensitive data such as notification tokens is included in the export file, please keep it carefully.",
alertNoFile: "Please select a file to import.",
alertWrongFileType: "Please select a JSON file."
}

131
src/languages/ko-KR.js Normal file
View File

@@ -0,0 +1,131 @@
export default {
languageName: "한국어",
checkEverySecond: "{0} 초마다 체크해요.",
"Avg.": "평균 ",
retriesDescription: "서비스가 중단된 후 알림을 보내기 전 최대 재시도 횟수",
ignoreTLSError: "HTTPS 웹사이트에서 TLS/SSL 에러 무시하기",
upsideDownModeDescription: "서버 상태를 반대로 표시해요. 서버가 작동하면 오프라인으로 표시할 거에요.",
maxRedirectDescription: "최대 리다이렉트 횟수에요. 0을 입력하면 리다이렉트를 꺼요.",
acceptedStatusCodesDescription: "응답 성공으로 간주할 상태 코드를 정해요.",
passwordNotMatchMsg: "비밀번호 재입력이 일치하지 않아요.",
notificationDescription: "모니터링에 알림을 설정할 수 있어요.",
keywordDescription: "Html 이나 JSON에서 대소문자를 구분해 키워드를 검색해요.",
pauseDashboardHome: "일시 정지",
deleteMonitorMsg: "정말 이 모니터링을 삭제할까요?",
deleteNotificationMsg: "정말 이 알림을 모든 모니터링에서 삭제할까요?",
resoverserverDescription: "Cloudflare가 기본 서버에요, 원한다면 언제나 다른 resolver 서버로 변경할 수 있어요.",
rrtypeDescription: "모니터링할 RR-Type을 선택해요.",
pauseMonitorMsg: "정말 이 모니터링을 일시 정지 할까요?",
Settings: "설정",
Dashboard: "대시보드",
"New Update": "새로운 업데이트",
Language: "언어",
Appearance: "외형",
Theme: "테마",
General: "일반",
Version: "버전",
"Check Update On GitHub": "깃허브에서 업데이트 확인",
List: "목록",
Add: "추가",
"Add New Monitor": "새로운 모니터링 추가하기",
"Quick Stats": "간단한 정보",
Up: "온라인",
Down: "오프라인",
Pending: "대기 중",
Unknown: "알 수 없음",
Pause: "일시 정지",
Name: "이름",
Status: "상태",
DateTime: "날짜",
Message: "메시지",
"No important events": "중요 이벤트 없음",
Resume: "재개",
Edit: "수정",
Delete: "삭제",
Current: "현재",
Uptime: "업타임",
"Cert Exp.": "인증서 만료",
days: "일",
day: "일",
"-day": "-일",
hour: "시간",
"-hour": "-시간",
Response: "응답",
Ping: "핑",
"Monitor Type": "모니터링 종류",
Keyword: "키워드",
"Friendly Name": "이름",
URL: "URL",
Hostname: "호스트네임",
Port: "포트",
"Heartbeat Interval": "하트비트 주기",
Retries: "재시도",
Advanced: "고급",
"Upside Down Mode": "상태 반전 모드",
"Max. Redirects": "최대 리다이렉트",
"Accepted Status Codes": "응답 성공 상태 코드",
Save: "저장",
Notifications: "알림",
"Not available, please setup.": "존재하지 않아요, 새로운거 하나 만드는건 어때요?",
"Setup Notification": "알림 설정",
Light: "라이트",
Dark: "다크",
Auto: "자동",
"Theme - Heartbeat Bar": "테마 - 하트비트 바",
Normal: "기본값",
Bottom: "가운데",
None: "제거",
Timezone: "시간대",
"Search Engine Visibility": "검색 엔진 활성화",
"Allow indexing": "인덱싱 허용",
"Discourage search engines from indexing site": "검색 엔진 인덱싱 거부",
"Change Password": "비밀번호 변경",
"Current Password": "기존 비밀번호",
"New Password": "새로운 비밀번호",
"Repeat New Password": "새로운 비밀번호 재입력",
"Update Password": "비밀번호 변경",
"Disable Auth": "인증 끄기",
"Enable Auth": "인증 켜기",
Logout: "로그아웃",
Leave: "나가기",
"I understand, please disable": "기능에 대해 이해했으니 꺼주세요.",
Confirm: "확인",
Yes: "확인",
No: "취소",
Username: "이름",
Password: "비밀번호",
"Remember me": "비밀번호 기억하기",
Login: "로그인",
"No Monitors, please": "모니터링이 없어요,",
"add one": "하나 추가해봐요",
"Notification Type": "알림 종류",
Email: "이메일",
Test: "테스트",
"Certificate Info": "인증서 정보",
"Resolver Server": "Resolver 서버",
"Resource Record Type": "자원 레코드 유형",
"Last Result": "최근 결과",
"Create your admin account": "관리자 계정 만들기",
"Repeat Password": "비밀번호 재입력",
respTime: "응답 시간 (ms)",
notAvailableShort: "N/A",
Create: "Create",
clearEventsMsg: "Are you sure want to delete all events for this monitor?",
clearHeartbeatsMsg: "Are you sure want to delete all heartbeats for this monitor?",
confirmClearStatisticsMsg: "Are you sure want to delete ALL statistics?",
"Clear Data": "Clear Data",
Events: "Events",
Heartbeats: "Heartbeats",
"Auto Get": "Auto Get",
enableDefaultNotificationDescription: "For every new monitor this notification will be enabled by default. You can still disable the notification separately for each monitor.",
"Default enabled": "Default enabled",
"Also apply to existing monitors": "Also apply to existing monitors",
"Import/Export Backup": "Import/Export Backup",
Export: "Export",
Import: "Import",
backupDescription: "You can backup all monitors and all notifications into a JSON file.",
backupDescription2: "PS: History and event data is not included.",
backupDescription3: "Sensitive data such as notification tokens is included in the export file, please keep it carefully.",
alertNoFile: "Please select a file to import.",
alertWrongFileType: "Please select a JSON file."
}

131
src/languages/nl-NL.js Normal file
View File

@@ -0,0 +1,131 @@
export default {
languageName: "Nederlands",
checkEverySecond: "Controleer elke {0} seconden.",
"Avg.": "Gem. ",
retriesDescription: "Maximum aantal nieuwe pogingen voordat de service wordt gemarkeerd als niet beschikbaar en er een melding wordt verzonden",
ignoreTLSError: "Negeer TLS/SSL-fout voor HTTPS-websites",
upsideDownModeDescription: "Draai de status om. Als de service bereikbaar is, is deze OFFLINE.",
maxRedirectDescription: "Maximaal aantal te volgen omleidingen. Stel in op 0 om omleidingen uit te schakelen.",
acceptedStatusCodesDescription: "Selecteer statuscodes die als een succesvol antwoord worden beschouwd.",
passwordNotMatchMsg: "Het herhaalwachtwoord komt niet overeen.",
notificationDescription: "Wijs a.u.b. een melding toe aan de monitor(s) om het te laten werken.",
keywordDescription: "Zoek trefwoord in gewone html of JSON-response en het is hoofdlettergevoelig",
pauseDashboardHome: "Gepauzeerd",
deleteMonitorMsg: "Weet u zeker dat u deze monitor wilt verwijderen?",
deleteNotificationMsg: "Weet u zeker dat u deze melding voor alle monitoren wilt verwijderen?",
resoverserverDescription: "Cloudflare is de standaardserver, u kunt de resolver server op elk moment wijzigen.",
rrtypeDescription: "Selecteer het RR-type dat u wilt monitoren",
pauseMonitorMsg: "Weet je zeker dat je wilt pauzeren?",
Settings: "Instellingen",
Dashboard: "Dashboard",
"New Update": "Nieuwe update",
Language: "Taal",
Appearance: "Weergave",
Theme: "Thema",
General: "Algemeen",
Version: "Versie",
"Check Update On GitHub": "Controleer voor updates op GitHub",
List: "Lijst",
Add: "Toevoegen",
"Add New Monitor": "Nieuwe monitor toevoegen",
"Quick Stats": "Snelle statistieken",
Up: "Online",
Down: "Offline",
Pending: "In afwachting",
Unknown: "Onbekend",
Pause: "Pauze",
Name: "Naam",
Status: "Status",
DateTime: "Datum Tijd",
Message: "Bericht",
"No important events": "Geen belangrijke gebeurtenissen",
Resume: "Hervat",
Edit: "Wijzigen",
Delete: "Verwijderen",
Current: "Huidig",
Uptime: "Uptime",
"Cert Exp.": "Cert. verl.",
days: "dagen",
day: "dag",
"-day": "-dag",
hour: "uur",
"-hour": "-uur",
Response: "Antwoord",
Ping: "Ping",
"Monitor Type": "Monitortype:",
Keyword: "Trefwoord",
"Friendly Name": "Vriendelijke naam",
URL: "URL",
Hostname: "Hostnaam",
Port: "Poort",
"Heartbeat Interval": "Hartslaginterval",
Retries: "Pogingen",
Advanced: "Geavanceerd",
"Upside Down Mode": "Ondersteboven modus",
"Max. Redirects": "Max. Omleidingen",
"Accepted Status Codes": "Geaccepteerde statuscodes",
Save: "Opslaan",
Notifications: "Meldingen",
"Not available, please setup.": "Niet beschikbaar, stel a.u.b. in.",
"Setup Notification": "Melding instellen",
Light: "Licht",
Dark: "Donker",
Auto: "Auto",
"Theme - Heartbeat Bar": "Thema - Hartslagbalk",
Normal: "Normaal",
Bottom: "Onderkant",
None: "Geen",
Timezone: "Tijdzone",
"Search Engine Visibility": "Zichtbaarheid voor zoekmachines",
"Allow indexing": "Indexering toestaan",
"Discourage search engines from indexing site": "Ontmoedig zoekmachines om de site te indexeren",
"Change Password": "Verander wachtwoord",
"Current Password": "Huidig wachtwoord",
"New Password": "Nieuw wachtwoord",
"Repeat New Password": "Herhaal nieuw wachtwoord",
"Update Password": "Vernieuw wachtwoord",
"Disable Auth": "Autorisatie uitschakelen",
"Enable Auth": "Autorisatie inschakelen",
Logout: "Uitloggen",
Leave: "Vertrekken",
"I understand, please disable": "Ik begrijp het, schakel a.u.b. uit",
Confirm: "Bevestigen",
Yes: "Ja",
No: "Nee",
Username: "Gebruikersnaam",
Password: "Wachtwoord",
"Remember me": "Wachtwoord onthouden",
Login: "Inloggen",
"No Monitors, please": "Geen monitoren, ",
"add one": "voeg een toe",
"Notification Type": "Melding type",
Email: "E-mail",
Test: "Testen",
"Certificate Info": "Certificaat informatie",
"Resolver Server": "Resolver Server",
"Resource Record Type": "Type bronrecord",
"Last Result": "Laatste resultaat",
"Create your admin account": "Maak uw beheerdersaccount aan",
"Repeat Password": "Herhaal wachtwoord",
respTime: "resp. tijd (ms)",
notAvailableShort: "N.v.t.",
Create: "Create",
clearEventsMsg: "Are you sure want to delete all events for this monitor?",
clearHeartbeatsMsg: "Are you sure want to delete all heartbeats for this monitor?",
confirmClearStatisticsMsg: "Are you sure want to delete ALL statistics?",
"Clear Data": "Clear Data",
Events: "Events",
Heartbeats: "Heartbeats",
"Auto Get": "Auto Get",
enableDefaultNotificationDescription: "For every new monitor this notification will be enabled by default. You can still disable the notification separately for each monitor.",
"Default enabled": "Default enabled",
"Also apply to existing monitors": "Also apply to existing monitors",
"Import/Export Backup": "Import/Export Backup",
Export: "Export",
Import: "Import",
backupDescription: "You can backup all monitors and all notifications into a JSON file.",
backupDescription2: "PS: History and event data is not included.",
backupDescription3: "Sensitive data such as notification tokens is included in the export file, please keep it carefully.",
alertNoFile: "Please select a file to import.",
alertWrongFileType: "Please select a JSON file."
}

131
src/languages/pl.js Normal file
View File

@@ -0,0 +1,131 @@
export default {
languageName: "Polski",
checkEverySecond: "Sprawdzaj co {0} sekund.",
"Avg.": "Średnia ",
retriesDescription: "Maksymalna liczba powtórzeń, zanim usługa zostanie oznaczona jako wyłączona i zostanie wysłane powiadomienie",
ignoreTLSError: "Ignoruj błąd TLS/SSL dla stron HTTPS",
upsideDownModeDescription: "Odwróć status do góry nogami. Jeśli usługa jest osiągalna, to jest oznaczona jako niedostępna.",
maxRedirectDescription: "Maksymalna liczba przekierowań do wykonania. Ustaw na 0, aby wyłączyć przekierowania.",
acceptedStatusCodesDescription: "Wybierz kody stanu, które są uważane za udaną odpowiedź.",
passwordNotMatchMsg: "Powtórzone hasło nie pasuje.",
notificationDescription: "Proszę przypisać powiadomienie do monitora(ów), aby zadziałało.",
keywordDescription: "Wyszukiwanie słów kluczowych w zwykłym html lub odpowiedzi JSON. Wielkość liter ma znaczenie.",
pauseDashboardHome: "Pauza",
deleteMonitorMsg: "Czy na pewno chcesz usunąć ten monitor?",
deleteNotificationMsg: "Czy na pewno chcesz usunąć to powiadomienie dla wszystkich monitorów?",
resoverserverDescription: "Cloudflare jest domyślnym serwerem, możesz zmienić serwer resolver w każdej chwili.",
rrtypeDescription: "Wybierz RR-Type który chcesz monitorować",
pauseMonitorMsg: "Czy na pewno chcesz wstrzymać?",
Settings: "Ustawienia",
Dashboard: "Panel",
"New Update": "Nowa aktualizacja",
Language: "Język",
Appearance: "Wygląd",
Theme: "Motyw",
General: "Ogólne",
Version: "Wersja",
"Check Update On GitHub": "Sprawdź aktualizację na GitHub.",
List: "Lista",
Add: "Dodaj",
"Add New Monitor": "Dodaj nowy monitor",
"Quick Stats": "Szybkie statystyki",
Up: "Online",
Down: "Offline",
Pending: "Oczekujący",
Unknown: "Nieznane",
Pause: "Pauza",
Name: "Nazwa",
Status: "Status",
DateTime: "Data i godzina",
Message: "Wiadomość",
"No important events": "Brak ważnych wydarzeń",
Resume: "Wznów",
Edit: "Edytuj",
Delete: "Usuń",
Current: "aktualny",
Uptime: "Czas pracy",
"Cert Exp.": "Wygaśnięcie certyfikatu",
days: "dni",
day: "dzień",
"-day": " dni",
hour: "godzina",
"-hour": " godziny",
Response: "Odpowiedź",
Ping: "Ping",
"Monitor Type": "Typ monitora",
Keyword: "Słowo kluczowe",
"Friendly Name": "Przyjazna nazwa",
URL: "URL",
Hostname: "Nazwa hosta",
Port: "Port",
"Heartbeat Interval": "Interwał bicia serca",
Retries: "Prób",
Advanced: "Zaawansowane",
"Upside Down Mode": "Tryb do góry nogami",
"Max. Redirects": "Maks. przekierowania",
"Accepted Status Codes": "Akceptowane kody statusu",
Save: "Zapisz",
Notifications: "Powiadomienia",
"Not available, please setup.": "Niedostępne, proszę skonfigurować.",
"Setup Notification": "Konfiguracja powiadomień",
Light: "Jasny",
Dark: "Ciemny",
Auto: "Automatyczny",
"Theme - Heartbeat Bar": "Motyw - pasek bicia serca",
Normal: "Normalne",
Bottom: "Na dole",
None: "Brak",
Timezone: "Strefa czasowa",
"Search Engine Visibility": "Widoczność w wyszukiwarce",
"Allow indexing": "Pozwól na indeksowanie",
"Discourage search engines from indexing site": "Zniechęcaj wyszukiwarki do indeksowania strony",
"Change Password": "Zmień hasło",
"Current Password": "Aktualne hasło",
"New Password": "Nowe hasło",
"Repeat New Password": "Powtórz nowe hasło",
"Update Password": "Zaktualizuj hasło",
"Disable Auth": "Wyłącz autoryzację",
"Enable Auth": "Włącz autoryzację ",
Logout: "Wyloguj się",
Leave: "Zostaw",
"I understand, please disable": "Rozumiem, proszę wyłączyć",
Confirm: "Potwierdź",
Yes: "Tak",
No: "Nie",
Username: "Nazwa użytkownika",
Password: "Hasło",
"Remember me": "Zapamiętaj mnie",
Login: "Zaloguj się",
"No Monitors, please": "Brak monitorów, proszę",
"add one": "dodaj jeden",
"Notification Type": "Typ powiadomienia",
Email: "Email",
Test: "Test",
"Certificate Info": "Informacje o certyfikacie",
"Resolver Server": "Server resolver",
"Resource Record Type": "Typ rekordu zasobów",
"Last Result": "Ostatni wynik",
"Create your admin account": "Utwórz swoje konto administratora",
"Repeat Password": "Powtórz hasło",
respTime: "Czas odp. (ms)",
notAvailableShort: "N/A",
Create: "Stwórz",
clearEventsMsg: "Are you sure want to delete all events for this monitor?",
clearHeartbeatsMsg: "Are you sure want to delete all heartbeats for this monitor?",
confirmClearStatisticsMsg: "Are you sure want to delete ALL statistics?",
"Clear Data": "Clear Data",
Events: "Events",
Heartbeats: "Heartbeats",
"Auto Get": "Auto Get",
enableDefaultNotificationDescription: "For every new monitor this notification will be enabled by default. You can still disable the notification separately for each monitor.",
"Default enabled": "Default enabled",
"Also apply to existing monitors": "Also apply to existing monitors",
"Import/Export Backup": "Import/Export Backup",
Export: "Export",
Import: "Import",
backupDescription: "You can backup all monitors and all notifications into a JSON file.",
backupDescription2: "PS: History and event data is not included.",
backupDescription3: "Sensitive data such as notification tokens is included in the export file, please keep it carefully.",
alertNoFile: "Please select a file to import.",
alertWrongFileType: "Please select a JSON file."
}

131
src/languages/ru-RU.js Normal file
View File

@@ -0,0 +1,131 @@
export default {
languageName: "Русский",
checkEverySecond: "Проверять каждые {0} секунд.",
"Avg.": "Средн. ",
retriesDescription: "Максимальное количество попыток перед пометкой сервиса как недоступного и отправкой уведомления",
ignoreTLSError: "Игнорировать ошибку TLS/SSL для HTTPS сайтов",
upsideDownModeDescription: "Реверс статуса сервиса. Если сервис доступен, то он помечается как НЕДОСТУПНЫЙ.",
maxRedirectDescription: "Максимальное количество перенаправлений. Поставьте 0, чтобы отключить перенаправления.",
acceptedStatusCodesDescription: "Выберите коды статусов, которые должны считаться за успешный ответ.",
passwordNotMatchMsg: "Повтор пароля не совпадает.",
notificationDescription: "Привяжите уведомления к мониторам.",
keywordDescription: "Поиск слова в чистом HTML или в JSON-ответе (чувствительно к регистру)",
pauseDashboardHome: "Пауза",
deleteMonitorMsg: "Вы действительно хотите удалить данный монитор?",
deleteNotificationMsg: "Вы действительно хотите удалить это уведомление для всех мониторов?",
resoverserverDescription: "Cloudflare является сервером по умолчанию. Вы всегда можете сменить данный сервер.",
rrtypeDescription: "Выберите тип ресурсной записи, который вы хотите отслеживать",
pauseMonitorMsg: "Вы действительно хотите поставить на паузу?",
Settings: "Настройки",
Dashboard: "Панель",
"New Update": "Обновление",
Language: "Язык",
Appearance: "Внешний вид",
Theme: "Тема",
General: "Общее",
Version: "Версия",
"Check Update On GitHub": "Проверить обновления на GitHub",
List: "Список",
Add: "Добавить",
"Add New Monitor": "Новый монитор",
"Quick Stats": "Статистика",
Up: "Доступно",
Down: "Недоступно",
Pending: "Ожидание",
Unknown: "Неизвестно",
Pause: "Пауза",
Name: "Имя",
Status: "Статус",
DateTime: "Дата и время",
Message: "Сообщение",
"No important events": "Важных событий нет",
Resume: "Возобновить",
Edit: "Изменить",
Delete: "Удалить",
Current: "Текущий",
Uptime: "Аптайм",
"Cert Exp.": "Сертификат просрочен",
days: "дней",
day: "день",
"-day": " дней",
hour: "час",
"-hour": " часа",
Response: "Ответ",
Ping: "Пинг",
"Monitor Type": "Тип монитора",
Keyword: "Слово",
"Friendly Name": "Имя",
URL: "URL",
Hostname: "Имя хоста",
Port: "Порт",
"Heartbeat Interval": "Частота опроса",
Retries: "Попыток",
Advanced: "Дополнительно",
"Upside Down Mode": "Режим реверса статуса",
"Max. Redirects": "Макс. перенаправлений",
"Accepted Status Codes": "Допустимые коды статуса",
Save: "Сохранить",
Notifications: "Уведомления",
"Not available, please setup.": "Доступных уведомлений нет, необходима настройка.",
"Setup Notification": "Настроить уведомления",
Light: "Светлая",
Dark: "Тёмная",
Auto: "Авто",
"Theme - Heartbeat Bar": "Тема - Полоса частоты опроса",
Normal: "Обычный",
Bottom: "Снизу",
None: "Отсутствует",
Timezone: "Часовой пояс",
"Search Engine Visibility": "Видимость поисковым движком",
"Allow indexing": "Разрешить индексирование",
"Discourage search engines from indexing site": "Не позволять индексировать сайт",
"Change Password": "Сменить пароль",
"Current Password": "Текущий пароль",
"New Password": "Новый пароль",
"Repeat New Password": "Повтор нового пароля",
"Update Password": "Обновить пароль",
"Disable Auth": "Отключить авторизацию",
"Enable Auth": "Включить авторизацию",
Logout: "Выйти",
Leave: "Отмена",
"I understand, please disable": "Я понимаю, всё равно отключить",
Confirm: "Подтвердить",
Yes: "Да",
No: "Нет",
Username: "Логин",
Password: "Пароль",
"Remember me": "Запомнить меня",
Login: "Вход в систему",
"No Monitors, please": "Мониторов нет, пожалуйста",
"add one": "создайте новый",
"Notification Type": "Тип уведомления",
Email: "Почта",
Test: "Проверка",
"Certificate Info": "Информация о сертификате",
"Resolver Server": "DNS сервер",
"Resource Record Type": "Тип ресурсной записи",
"Last Result": "Последний результат",
"Create your admin account": "Создайте аккаунт администратора",
"Repeat Password": "Повторите пароль",
respTime: "Resp. Time (ms)",
notAvailableShort: "N/A",
Create: "Create",
clearEventsMsg: "Are you sure want to delete all events for this monitor?",
clearHeartbeatsMsg: "Are you sure want to delete all heartbeats for this monitor?",
confirmClearStatisticsMsg: "Are you sure want to delete ALL statistics?",
"Clear Data": "Clear Data",
Events: "Events",
Heartbeats: "Heartbeats",
"Auto Get": "Auto Get",
enableDefaultNotificationDescription: "For every new monitor this notification will be enabled by default. You can still disable the notification separately for each monitor.",
"Default enabled": "Default enabled",
"Also apply to existing monitors": "Also apply to existing monitors",
"Import/Export Backup": "Import/Export Backup",
Export: "Export",
Import: "Import",
backupDescription: "You can backup all monitors and all notifications into a JSON file.",
backupDescription2: "PS: History and event data is not included.",
backupDescription3: "Sensitive data such as notification tokens is included in the export file, please keep it carefully.",
alertNoFile: "Please select a file to import.",
alertWrongFileType: "Please select a JSON file."
}

131
src/languages/sr-latn.js Normal file
View File

@@ -0,0 +1,131 @@
export default {
languageName: "Srpski",
checkEverySecond: "Proveri svakih {0} sekundi.",
"Avg.": "Prosečni ",
retriesDescription: "Maksimum pokušaja pre nego što se servis obeleži kao neaktivan i pošalje se obaveštenje.",
ignoreTLSError: "Ignoriši TLS/SSL greške za HTTPS veb stranice.",
upsideDownModeDescription: "Obrnite status. Ako je servis dostupan, onda je obeležen kao neaktivan.",
maxRedirectDescription: "Maksimani broj preusmerenja da se prate. Postavite na 0 da bi se isključila preusmerenja.",
acceptedStatusCodesDescription: "Odaberite statusne kodove koji se smatraju uspešnim odgovorom.",
passwordNotMatchMsg: "Ponovljena lozinka se ne poklapa.",
notificationDescription: "Molim Vas postavite obaveštenje za masmatrače da bise aktivirali.",
keywordDescription: "Pretraži ključnu reč u čistom html ili JSON odgovoru sa osetljivim velikim i malim slovima",
pauseDashboardHome: "Pauziraj",
deleteMonitorMsg: "Da li ste sigurni da želite da obrišete ovog posmatrača?",
deleteNotificationMsg: "Da li ste sigurni d aželite da uklonite ovo obaveštenje za sve posmatrače?",
resoverserverDescription: "Cloudflare je podrazumevani server. Možete promeniti server za raszrešavanje u bilo kom trenutku.",
rrtypeDescription: "Odaberite RR-Type koji želite da posmatrate",
pauseMonitorMsg: "Da li ste sigurni da želite da pauzirate?",
Settings: "Podešavanja",
Dashboard: "Komandna tabla",
"New Update": "Nova verzija",
Language: "Jezik",
Appearance: "Izgled",
Theme: "Tema",
General: "Opšte",
Version: "Verzija",
"Check Update On GitHub": "Proverite novu verziju na GitHub-u",
List: "Lista",
Add: "Dodaj",
"Add New Monitor": "Dodaj novog posmatrača",
"Quick Stats": "Brze statistike",
Up: "Aktivno",
Down: "Neaktivno",
Pending: "Nerešeno",
Unknown: "Nepoznato",
Pause: "Pauziraj",
Name: "Ime",
Status: "Status",
DateTime: "Datum i vreme",
Message: "Poruka",
"No important events": "Nema bitnih događaja",
Resume: "Nastavi",
Edit: "Izmeni",
Delete: "Ukloni",
Current: "Trenutno",
Uptime: "Vreme rada",
"Cert Exp.": "Istek sert.",
days: "dana",
day: "dan",
"-day": "-dana",
hour: "sat",
"-hour": "-sata",
Response: "Odgovor",
Ping: "Ping",
"Monitor Type": "Tip posmatrača",
Keyword: "Ključna reč",
"Friendly Name": "Prijateljsko ime",
URL: "URL",
Hostname: "Hostname",
Port: "Port",
"Heartbeat Interval": "Interval otkucaja srca",
Retries: "Pokušaji",
Advanced: "Napredno",
"Upside Down Mode": "Naopak mod",
"Max. Redirects": "Maks. preusmerenja",
"Accepted Status Codes": "Prihvaćeni statusni kodovi",
Save: "Sačuvaj",
Notifications: "Obaveštenja",
"Not available, please setup.": "Nije dostupno, molim Vas podesite.",
"Setup Notification": "Postavi obaveštenje",
Light: "Svetlo",
Dark: "Tamno",
Auto: "Automatsko",
"Theme - Heartbeat Bar": "Tema - Traka otkucaja srca",
Normal: "Normalno",
Bottom: "Dole",
None: "Isključeno",
Timezone: "Vremenska zona",
"Search Engine Visibility": "Vidljivost pretraživačima",
"Allow indexing": "Dozvoli indeksiranje",
"Discourage search engines from indexing site": "Odvraćajte pretraživače od indeksiranja sajta",
"Change Password": "Promeni lozinku",
"Current Password": "Trenutna lozinka",
"New Password": "Nova lozinka",
"Repeat New Password": "Ponovi novu lozinku",
"Update Password": "Izmeni lozinku",
"Disable Auth": "Isključi autentifikaciju",
"Enable Auth": "Uključi autentifikaciju",
Logout: "Odloguj se",
Leave: "Izađi",
"I understand, please disable": "Razumem, molim te isključi",
Confirm: "Potvrdi",
Yes: "Da",
No: "Ne",
Username: "Korisničko ime",
Password: "Lozinka",
"Remember me": "Zapamti me",
Login: "Uloguj se",
"No Monitors, please": "Bez posmatrača molim",
"add one": "dodaj jednog",
"Notification Type": "Tip obaveštenja",
Email: "E-pošta",
Test: "Test",
"Certificate Info": "Informacije sertifikata",
"Resolver Server": "Razrešivački server",
"Resource Record Type": "Tip zapisa resursa",
"Last Result": "Poslednji rezultat",
"Create your admin account": "Naprivi administratorski nalog",
"Repeat Password": "Ponovite lozinku",
respTime: "Vreme odg. (ms)",
notAvailableShort: "N/A",
Create: "Create",
clearEventsMsg: "Are you sure want to delete all events for this monitor?",
clearHeartbeatsMsg: "Are you sure want to delete all heartbeats for this monitor?",
confirmClearStatisticsMsg: "Are you sure want to delete ALL statistics?",
"Clear Data": "Clear Data",
Events: "Events",
Heartbeats: "Heartbeats",
"Auto Get": "Auto Get",
enableDefaultNotificationDescription: "For every new monitor this notification will be enabled by default. You can still disable the notification separately for each monitor.",
"Default enabled": "Default enabled",
"Also apply to existing monitors": "Also apply to existing monitors",
"Import/Export Backup": "Import/Export Backup",
Export: "Export",
Import: "Import",
backupDescription: "You can backup all monitors and all notifications into a JSON file.",
backupDescription2: "PS: History and event data is not included.",
backupDescription3: "Sensitive data such as notification tokens is included in the export file, please keep it carefully.",
alertNoFile: "Please select a file to import.",
alertWrongFileType: "Please select a JSON file."
}

131
src/languages/sr.js Normal file
View File

@@ -0,0 +1,131 @@
export default {
languageName: "Српски",
checkEverySecond: "Провери сваких {0} секунди.",
"Avg.": "Просечни ",
retriesDescription: "Максимум покушаја пре него што се сервис обележи као неактиван и пошаље се обавештење.",
ignoreTLSError: "Игнориши TLS/SSL грешке за HTTPS веб странице.",
upsideDownModeDescription: "Обрните статус. Ако је сервис доступан, онда је обележен као неактиван.",
maxRedirectDescription: "Максимани број преусмерења да се прате. Поставите на 0 да би се искључила преусмерења.",
acceptedStatusCodesDescription: "Одаберите статусне кодове који се сматрају успешним одговором.",
passwordNotMatchMsg: "Поновљена лозинка се не поклапа.",
notificationDescription: "Молим Вас поставите обавештење за масматраче да бисе активирали.",
keywordDescription: "Претражи кључну реч у чистом html или JSON одговору са осетљивим великим и малим словима",
pauseDashboardHome: "Паузирај",
deleteMonitorMsg: "Да ли сте сигурни да желите да обришете овог посматрача?",
deleteNotificationMsg: "Да ли сте сигурни д ажелите да уклоните ово обавештење за све посматраче?",
resoverserverDescription: "Cloudflare је подразумевани сервер. Можете променити сервер за расзрешавање у било ком тренутку.",
rrtypeDescription: "Одаберите RR-Type који желите да посматрате",
pauseMonitorMsg: "Да ли сте сигурни да желите да паузирате?",
Settings: "Подешавања",
Dashboard: "Командна табла",
"New Update": "Нова верзија",
Language: "Језик",
Appearance: "Изглед",
Theme: "Тема",
General: "Опште",
Version: "Верзија",
"Check Update On GitHub": "Проверите нову верзију на GitHub-у",
List: "Листа",
Add: "Додај",
"Add New Monitor": "Додај новог посматрача",
"Quick Stats": "Брзе статистике",
Up: "Активно",
Down: "Неактивно",
Pending: "Нерешено",
Unknown: "Непознато",
Pause: "Паузирај",
Name: "Име",
Status: "Статус",
DateTime: "Датум и време",
Message: "Порука",
"No important events": "Нема битних догађаја",
Resume: "Настави",
Edit: "Измени",
Delete: "Уклони",
Current: "Тренутно",
Uptime: "Време рада",
"Cert Exp.": "Истек серт.",
days: "дана",
day: "дан",
"-day": "-дана",
hour: "сат",
"-hour": "-сата",
Response: "Одговор",
Ping: "Пинг",
"Monitor Type": "Тип посматрача",
Keyword: "Кључна реч",
"Friendly Name": "Пријатељско име",
URL: "URL",
Hostname: "Hostname",
Port: "Порт",
"Heartbeat Interval": "Интервал откуцаја срца",
Retries: "Покушаји",
Advanced: "Напредно",
"Upside Down Mode": "Наопак мод",
"Max. Redirects": "Макс. преусмерења",
"Accepted Status Codes": "Прихваћени статусни кодови",
Save: "Сачувај",
Notifications: "Обавештења",
"Not available, please setup.": "Није доступно, молим Вас подесите.",
"Setup Notification": "Постави обавештење",
Light: "Светло",
Dark: "Тамно",
Auto: "Аутоматско",
"Theme - Heartbeat Bar": "Тема - Трака откуцаја срца",
Normal: "Нормално",
Bottom: "Доле",
None: "Искључено",
Timezone: "Временска зона",
"Search Engine Visibility": "Видљивост претраживачима",
"Allow indexing": "Дозволи индексирање",
"Discourage search engines from indexing site": "Одвраћајте претраживаче од индексирања сајта",
"Change Password": "Промени лозинку",
"Current Password": "Тренутна лозинка",
"New Password": "Нова лозинка",
"Repeat New Password": "Понови нову лозинку",
"Update Password": "Измени лозинку",
"Disable Auth": "Искључи аутентификацију",
"Enable Auth": "Укључи аутентификацију",
Logout: "Одлогуј се",
Leave: "Изађи",
"I understand, please disable": "Разумем, молим те искључи",
Confirm: "Потврди",
Yes: "Да",
No: "Не",
Username: "Корисничко име",
Password: "Лозинка",
"Remember me": "Запамти ме",
Login: "Улогуј се",
"No Monitors, please": "Без посматрача молим",
"add one": "додај једног",
"Notification Type": "Тип обавештења",
Email: "Е-пошта",
Test: "Тест",
"Certificate Info": "Информације сертификата",
"Resolver Server": "Разрешивачки сервер",
"Resource Record Type": "Тип записа ресурса",
"Last Result": "Последњи резултат",
"Create your admin account": "Наприви администраторски налог",
"Repeat Password": "Поновите лозинку",
respTime: "Време одг. (мс)",
notAvailableShort: "N/A",
Create: "Create",
clearEventsMsg: "Are you sure want to delete all events for this monitor?",
clearHeartbeatsMsg: "Are you sure want to delete all heartbeats for this monitor?",
confirmClearStatisticsMsg: "Are you sure want to delete ALL statistics?",
"Clear Data": "Clear Data",
Events: "Events",
Heartbeats: "Heartbeats",
"Auto Get": "Auto Get",
enableDefaultNotificationDescription: "For every new monitor this notification will be enabled by default. You can still disable the notification separately for each monitor.",
"Default enabled": "Default enabled",
"Also apply to existing monitors": "Also apply to existing monitors",
"Import/Export Backup": "Import/Export Backup",
Export: "Export",
Import: "Import",
backupDescription: "You can backup all monitors and all notifications into a JSON file.",
backupDescription2: "PS: History and event data is not included.",
backupDescription3: "Sensitive data such as notification tokens is included in the export file, please keep it carefully.",
alertNoFile: "Please select a file to import.",
alertWrongFileType: "Please select a JSON file."
}

131
src/languages/sv-SE.js Normal file
View File

@@ -0,0 +1,131 @@
export default {
languageName: "Svenska",
checkEverySecond: "Uppdatera var {0} sekund.",
"Avg.": "Genomsnittligt ",
retriesDescription: "Max antal försök innan tjänsten markeras som nere och en notis skickas",
ignoreTLSError: "Ignorera TLS/SSL-fel för webbsidor med HTTPS",
upsideDownModeDescription: "Vänd upp och ner på statusen. Om tjänsten är nåbar visas den som NERE.",
maxRedirectDescription: "Max antal omdirigeringar att följa. Välj 0 för att avaktivera omdirigeringar.",
acceptedStatusCodesDescription: "Välj statuskoder som räknas som lyckade.",
passwordNotMatchMsg: "Det bekräftade lösenordet stämmer ej överens.",
notificationDescription: "Vänligen lägg till en notistjänst till dina övervakare.",
keywordDescription: "Sök efter nyckelord i ren HTML eller JSON-svar. Sökningen är skiftkänslig.",
pauseDashboardHome: "Pausa",
deleteMonitorMsg: "Är du säker på att du vill ta bort den här övervakningen?",
deleteNotificationMsg: "Är du säker på att du vill ta bort den här notisen för alla övervakare?",
resoverserverDescription: "Cloudflare är den förvalda servern. Du kan byta resolver när som helst.",
rrtypeDescription: "Välj den RR-typ du vill övervaka",
pauseMonitorMsg: "Är du säker på att du vill pausa?",
Settings: "Inställningar",
Dashboard: "Infopanel",
"New Update": "Ny uppdatering",
Language: "Språk",
Appearance: "Utseende",
Theme: "Tema",
General: "Allmänt",
Version: "Version",
"Check Update On GitHub": "Sök efter uppdatering på GitHub",
List: "Lista",
Add: "Lägg till",
"Add New Monitor": "Lägg Till Ny Övervakare",
"Quick Stats": "Snabbstatistik",
Up: "Uppe",
Down: "Nere",
Pending: "Pågående",
Unknown: "Okänt",
Pause: "Pausa",
Name: "Namn",
Status: "Status",
DateTime: "Datum & Tid",
Message: "Meddelande",
"No important events": "Inga viktiga händelser",
Resume: "Återuppta",
Edit: "Redigera",
Delete: "Ta bort",
Current: "Nuvarande",
Uptime: "Drifttid",
"Cert Exp.": "Certifikat utgår",
days: "dagar",
day: "dag",
"-day": " dagar",
hour: "timme",
"-hour": " timmar",
Response: "Svar",
Ping: "Ping",
"Monitor Type": "Övervakningstyp",
Keyword: "Nyckelord",
"Friendly Name": "Namn",
URL: "URL",
Hostname: "Värdnamn",
Port: "Port",
"Heartbeat Interval": "Hjärtslagsintervall",
Retries: "Försök",
Advanced: "Avancerat",
"Upside Down Mode": "Upp och ner-läge",
"Max. Redirects": "Max antal omdirigeringar",
"Accepted Status Codes": "Tillåtna statuskoder",
Save: "Spara",
Notifications: "Notiser",
"Not available, please setup.": "Ej tillgänglig, vänligen konfigurera.",
"Setup Notification": "Ny Notistjänst",
Light: "Ljust",
Dark: "Mörkt",
Auto: "Automatiskt",
"Theme - Heartbeat Bar": "Tema - Heartbeat Bar",
Normal: "Normal",
Bottom: "Botten",
None: "Tomt",
Timezone: "Tidszon",
"Search Engine Visibility": "Synlighet på Sökmotorer",
"Allow indexing": "Tillåt indexering",
"Discourage search engines from indexing site": "Hindra sökmotorer från att indexera sidan",
"Change Password": "Byt Lösenord",
"Current Password": "Nuvarande Lösenord",
"New Password": "Nytt Lösenord",
"Repeat New Password": "Upprepa Nytt Lösenord",
"Update Password": "Uppdatera Lösenord",
"Disable Auth": "Avaktivera Autentisering",
"Enable Auth": "Aktivera Autentisering",
Logout: "Logga ut",
Leave: "Lämna",
"I understand, please disable": "Jag förstår, vänligen avaktivera",
Confirm: "Bekräfta",
Yes: "Ja",
No: "Nej",
Username: "Användarnamn",
Password: "Lösenord",
"Remember me": "Kom ihåg mig",
Login: "Logga in",
"No Monitors, please": "Inga Övervakare, tack",
"add one": "lägg till en",
"Notification Type": "Notistyp",
Email: "Email",
Test: "Test",
"Certificate Info": "Certifikatsinfo",
"Resolver Server": "Resolverserver",
"Resource Record Type": "RR-typ",
"Last Result": "Senaste resultat",
"Create your admin account": "Skapa ditt administratörskonto",
"Repeat Password": "Upprepa Lösenord",
respTime: "Svarstid (ms)",
notAvailableShort: "Ej Tillg.",
Create: "Create",
clearEventsMsg: "Are you sure want to delete all events for this monitor?",
clearHeartbeatsMsg: "Are you sure want to delete all heartbeats for this monitor?",
confirmClearStatisticsMsg: "Are you sure want to delete ALL statistics?",
"Clear Data": "Clear Data",
Events: "Events",
Heartbeats: "Heartbeats",
"Auto Get": "Auto Get",
enableDefaultNotificationDescription: "For every new monitor this notification will be enabled by default. You can still disable the notification separately for each monitor.",
"Default enabled": "Default enabled",
"Also apply to existing monitors": "Also apply to existing monitors",
"Import/Export Backup": "Import/Export Backup",
Export: "Export",
Import: "Import",
backupDescription: "You can backup all monitors and all notifications into a JSON file.",
backupDescription2: "PS: History and event data is not included.",
backupDescription3: "Sensitive data such as notification tokens is included in the export file, please keep it carefully.",
alertNoFile: "Please select a file to import.",
alertWrongFileType: "Please select a JSON file."
}

131
src/languages/zh-CN.js Normal file
View File

@@ -0,0 +1,131 @@
export default {
languageName: "简体中文",
checkEverySecond: "检测频率 {0} 秒",
"Avg.": "平均",
retriesDescription: "最大重试失败次数",
ignoreTLSError: "忽略HTTPS站点的证书错误",
upsideDownModeDescription: "反向状态监控(状态码范围外为有效状态,反之为无效)",
maxRedirectDescription: "最大重定向次数,设置为 0 禁止重定向",
acceptedStatusCodesDescription: "选择被视为成功响应的状态码",
passwordNotMatchMsg: "两次密码输入不一致",
notificationDescription: "请为监控项配置消息通知",
keywordDescription: "检测响应内容中的关键字,区分大小写",
pauseDashboardHome: "暂停",
deleteMonitorMsg: "确定要删除此监控吗?",
deleteNotificationMsg: "确定要删除此消息通知吗?这将对所有监控生效。",
resoverserverDescription: "可自定义要使用的DNS服务器",
rrtypeDescription: "选择要监控的资源记录类型",
pauseMonitorMsg: "确定要暂停吗?",
Settings: "设置",
Dashboard: "仪表盘",
"New Update": "有新版本更新",
Language: "语言",
Appearance: "外观设置",
Theme: "主题",
General: "基本设置",
Version: "Version",
"Check Update On GitHub": "检查更新",
List: "列表",
Add: "添加",
"Add New Monitor": "创建监控项",
"Quick Stats": "状态速览",
Up: "正常",
Down: "故障",
Pending: "检测失败",
Unknown: "未知",
Pause: "暂停",
Name: "名称",
Status: "状态",
DateTime: "时间",
Message: "事件",
"No important events": "暂无重要事件",
Resume: "恢复",
Edit: "修改",
Delete: "删除",
Current: "当前",
Uptime: "可用率",
"Cert Exp.": "证书过期",
days: "天",
day: "天",
"-day": " 天",
hour: "小时",
"-hour": " 小时",
Response: "响应时长",
Ping: "Ping",
"Monitor Type": "监控类型",
Keyword: "关键字",
"Friendly Name": "自定义名称",
URL: "网址URL",
Hostname: "主机名",
Port: "端口号",
"Heartbeat Interval": "心跳间隔",
Retries: "重试次数",
Advanced: "高级选项",
"Upside Down Mode": "反向监控",
"Max. Redirects": "重定向次数",
"Accepted Status Codes": "有效状态码",
Save: "保存",
Notifications: "消息通知",
"Not available, please setup.": "无可用通道,请先设置",
"Setup Notification": "设置通知",
Light: "明亮",
Dark: "黑暗",
Auto: "自动",
"Theme - Heartbeat Bar": "状态显示",
Normal: "正常显示",
Bottom: "靠下显示",
None: "不显示",
Timezone: "时区",
"Search Engine Visibility": "搜索引擎设置",
"Allow indexing": "允许索引",
"Discourage search engines from indexing site": "阻止搜索引擎索引网站",
"Change Password": "修改密码",
"Current Password": "当前密码",
"New Password": "新的密码",
"Repeat New Password": "重复新的密码",
"Update Password": "更新密码",
"Disable Auth": "禁用身份验证",
"Enable Auth": "启用身份验证",
Logout: "退出",
Leave: "离开",
"I understand, please disable": "我已了解,继续禁用",
Confirm: "确认",
Yes: "确定",
No: "取消",
Username: "用户名",
Password: "密码",
"Remember me": "记住登录",
Login: "登录",
"No Monitors, please": "还没有监控项,",
"add one": "点击新增",
"Notification Type": "消息类型",
Email: "邮件",
Test: "测试一下",
"Certificate Info": "证书信息",
"Resolver Server": "解析服务器",
"Resource Record Type": "资源记录类型",
"Last Result": "Last Result",
"Create your admin account": "创建管理员账号",
"Repeat Password": "重复密码",
respTime: "Resp. Time (ms)",
notAvailableShort: "N/A",
Create: "创建",
clearEventsMsg: "确定要删除此监控项的所有事件吗?",
clearHeartbeatsMsg: "确定要删除此监控项的所有状态吗?",
confirmClearStatisticsMsg: "确定要删除所有统计信息吗?",
"Clear Data": "清除数据",
Events: "事件",
Heartbeats: "心跳",
"Auto Get": "自动获取",
enableDefaultNotificationDescription: "新的监控项将默认启用,你也可以在每个监控项中分别设置",
"Default enabled": "默认开启",
"Also apply to existing monitors": "应用到所有监控项",
"Import/Export Backup": "导入/导出备份",
Export: "导出",
Import: "导入",
backupDescription: "你可以将所有的监控项和消息通知备份到一个 JSON 文件中",
backupDescription2: "注意: 不包括历史状态和事件数据",
backupDescription3: "导出的文件中可能包含敏感信息,如消息通知的 Token 信息,请小心存放!",
alertNoFile: "请选择一个文件导入",
alertWrongFileType: "请选择一个 JSON 格式的文件"
}

View File

@@ -93,8 +93,8 @@ export default {
"No Monitors, please": "沒有監測器,請",
"add one": "新增",
"Notification Type": "通知類型",
"Email": "電郵",
"Test": "測試",
Email: "電郵",
Test: "測試",
keywordDescription: "搜索 HTML 或 JSON 裡是否有出現關鍵字(注意英文大細階)",
"Certificate Info": "憑證詳細資料",
deleteMonitorMsg: "是否確定刪除這個監測器",
@@ -103,4 +103,29 @@ export default {
"Resource Record Type": "DNS 記錄類型",
resoverserverDescription: "預設值為 Cloudflare DNS 伺服器,你可以轉用其他 DNS 伺服器。",
rrtypeDescription: "請選擇 DNS 記錄類型",
pauseMonitorMsg: "是否確定暫停?",
"Last Result": "最後結果",
"Create your admin account": "製作你的管理員帳號",
"Repeat Password": "重複密碼",
respTime: "反應時間 (ms)",
notAvailableShort: "N/A",
Create: "建立",
clearEventsMsg: "是否確定刪除這個監測器的所有事件?",
clearHeartbeatsMsg: "是否確定刪除這個監測器的所有脈搏資料?",
confirmClearStatisticsMsg: "是否確定刪除所有監測器的脈搏資料?(您的監測器會繼續正常運作)",
"Clear Data": "清除資料",
Events: "事件",
Heartbeats: "脈搏",
"Auto Get": "自動獲取",
enableDefaultNotificationDescription: "新增監測器時這個通知會預設啟用,當然每個監測器亦可分別控制開關。",
"Default enabled": "預設通知",
"Also apply to existing monitors": "同時取用至目前所有監測器",
"Import/Export Backup": "匯入/匯出 備份",
Export: "匯出",
Import: "匯入",
backupDescription: "您可以備份所有監測器及所有通知。",
backupDescription2: "註:此備份不包括歷史記錄。",
backupDescription3: "此備份可能包含了一些敏感資料如通知裡的 Token請小心保存備份。",
alertNoFile: "請選擇一個檔案",
alertWrongFileType: "請選擇 JSON 檔案"
}

View File

@@ -26,7 +26,20 @@ import { appName } from "./util.ts";
import en from "./languages/en";
import zhHK from "./languages/zh-HK";
import deDE from "./languages/de-DE";
import fr from "./languages/fr";
import nlNL from "./languages/nl-NL";
import esEs from "./languages/es-ES";
import frFR from "./languages/fr-FR";
import itIT from "./languages/it-IT";
import ja from "./languages/ja";
import daDK from "./languages/da-DK";
import sr from "./languages/sr";
import srLatn from "./languages/sr-latn";
import svSE from "./languages/sv-SE";
import koKR from "./languages/ko-KR";
import ruRU from "./languages/ru-RU";
import zhCN from "./languages/zh-CN";
import pl from "./languages/pl"
import etEE from "./languages/et-EE"
const routes = [
{
@@ -93,7 +106,20 @@ const languageList = {
en,
"zh-HK": zhHK,
"de-DE": deDE,
"fr": fr,
"nl-NL": nlNL,
"es-ES": esEs,
"fr-FR": frFR,
"it-IT": itIT,
"ja": ja,
"da-DK": daDK,
"sr": sr,
"sr-latn": srLatn,
"sv-SE": svSE,
"ko-KR": koKR,
"ru-RU": ruRU,
"zh-CN": zhCN,
"pl": pl,
"et-EE": etEE,
};
const i18n = createI18n({

View File

@@ -107,8 +107,8 @@ export default {
}
});
socket.on("heartbeatList", (monitorID, data) => {
if (! (monitorID in this.heartbeatList)) {
socket.on("heartbeatList", (monitorID, data, overwrite = false) => {
if (! (monitorID in this.heartbeatList) || overwrite) {
this.heartbeatList[monitorID] = data;
} else {
this.heartbeatList[monitorID] = data.concat(this.heartbeatList[monitorID])
@@ -127,8 +127,8 @@ export default {
this.certInfoList[monitorID] = JSON.parse(data)
});
socket.on("importantHeartbeatList", (monitorID, data) => {
if (! (monitorID in this.importantHeartbeatList)) {
socket.on("importantHeartbeatList", (monitorID, data, overwrite) => {
if (! (monitorID in this.importantHeartbeatList) || overwrite) {
this.importantHeartbeatList[monitorID] = data;
} else {
this.importantHeartbeatList[monitorID] = data.concat(this.importantHeartbeatList[monitorID])
@@ -254,6 +254,21 @@ export default {
this.importantHeartbeatList = {}
},
uploadBackup(uploadedJSON, callback) {
socket.emit("uploadBackup", uploadedJSON, callback)
},
clearEvents(monitorID, callback) {
socket.emit("clearEvents", monitorID, callback)
},
clearHeartbeats(monitorID, callback) {
socket.emit("clearHeartbeats", monitorID, callback)
},
clearStatistics(callback) {
socket.emit("clearStatistics", callback)
},
},
computed: {

View File

@@ -11,7 +11,7 @@ export default {
mounted() {
// Default Light
if (! this.userTheme) {
this.userTheme = "light";
this.userTheme = "auto";
}
// Default Heartbeat Bar

View File

@@ -5,7 +5,7 @@
<div>
<router-link to="/add" class="btn btn-primary mb-3"><font-awesome-icon icon="plus" /> {{ $t("Add New Monitor") }}</router-link>
</div>
<MonitorList scrollbar="true" />
<MonitorList :scrollbar="true" />
</div>
<div class="col-12 col-md-7 col-xl-8 mb-3">

View File

@@ -38,7 +38,7 @@
</thead>
<tbody>
<tr v-for="(beat, index) in displayedRecords" :key="index" :class="{ 'shadow-box': $root.windowWidth <= 550}">
<td>{{ beat.name }}</td>
<td><router-link :to="`/dashboard/${beat.monitorID}`">{{ beat.name }}</router-link></td>
<td><Status :status="beat.status" /></td>
<td :class="{ 'border-0':! beat.msg}"><Datetime :value="beat.time" /></td>
<td class="border-0">{{ beat.msg }}</td>

View File

@@ -133,6 +133,23 @@
</div>
<div class="shadow-box table-shadow-box">
<div class="dropdown dropdown-clear-data">
<button class="btn btn-sm btn-outline-danger dropdown-toggle" type="button" data-bs-toggle="dropdown">
<font-awesome-icon icon="trash" /> {{ $t("Clear Data") }}
</button>
<ul class="dropdown-menu dropdown-menu-end">
<li>
<button type="button" class="dropdown-item" @click="clearEventsDialog">
{{ $t("Events") }}
</button>
</li>
<li>
<button type="button" class="dropdown-item" @click="clearHeartbeatsDialog">
{{ $t("Heartbeats") }}
</button>
</li>
</ul>
</div>
<table class="table table-borderless table-hover">
<thead>
<tr>
@@ -172,6 +189,14 @@
<Confirm ref="confirmDelete" btn-style="btn-danger" :yes-text="$t('Yes')" :no-text="$t('No')" @yes="deleteMonitor">
{{ $t("deleteMonitorMsg") }}
</Confirm>
<Confirm ref="confirmClearEvents" btn-style="btn-danger" :yes-text="$t('Yes')" :no-text="$t('No')" @yes="clearEvents">
{{ $t("clearEventsMsg") }}
</Confirm>
<Confirm ref="confirmClearHeartbeats" btn-style="btn-danger" :yes-text="$t('Yes')" :no-text="$t('No')" @yes="clearHeartbeats">
{{ $t("clearHeartbeatsMsg") }}
</Confirm>
</div>
</transition>
</template>
@@ -238,7 +263,7 @@ export default {
return this.lastHeartBeat.ping;
}
return "N/A"
return this.$t("notAvailableShort")
},
avgPing() {
@@ -246,11 +271,12 @@ export default {
return this.$root.avgPingList[this.monitor.id];
}
return "N/A"
return this.$t("notAvailableShort")
},
importantHeartBeatList() {
if (this.$root.importantHeartbeatList[this.monitor.id]) {
// eslint-disable-next-line vue/no-side-effects-in-computed-properties
this.heartBeatList = this.$root.importantHeartbeatList[this.monitor.id];
return this.$root.importantHeartbeatList[this.monitor.id]
}
@@ -313,6 +339,14 @@ export default {
this.$refs.confirmDelete.show();
},
clearEventsDialog() {
this.$refs.confirmClearEvents.show();
},
clearHeartbeatsDialog() {
this.$refs.confirmClearHeartbeats.show();
},
deleteMonitor() {
this.$root.deleteMonitor(this.monitor.id, (res) => {
if (res.ok) {
@@ -324,6 +358,21 @@ export default {
})
},
clearEvents() {
this.$root.clearEvents(this.monitor.id, (res) => {
if (! res.ok) {
toast.error(res.msg);
}
})
},
clearHeartbeats() {
this.$root.clearHeartbeats(this.monitor.id, (res) => {
if (! res.ok) {
toast.error(res.msg);
}
})
},
},
}
</script>
@@ -340,16 +389,20 @@ export default {
@media (max-width: 550px) {
.functions {
text-align: center;
}
button, a {
margin-left: 10px !important;
margin-right: 10px !important;
button, a {
margin-left: 10px !important;
margin-right: 10px !important;
}
}
.ping-chart-wrapper {
padding: 10px !important;
}
.dropdown-clear-data {
margin-bottom: 10px;
}
}
@media (max-width: 400px) {
@@ -364,6 +417,13 @@ export default {
padding-left: 25px;
padding-right: 25px;
}
.dropdown-clear-data {
button {
display: block;
padding-top: 4px;
}
}
}
.url {
@@ -417,10 +477,30 @@ table {
color: black;
}
.dropdown-clear-data {
float: right;
}
.dark {
.keyword {
color: $dark-font-color;
}
.dropdown-clear-data {
ul {
background-color: $dark-bg;
border-color: $dark-bg2;
border-width: 2px;
li button {
color: $dark-font-color;
}
li button:hover {
background-color: $dark-bg2;
}
}
}
}
</style>

View File

@@ -10,7 +10,7 @@
<div class="my-3">
<label for="type" class="form-label">{{ $t("Monitor Type") }}</label>
<select id="type" v-model="monitor.type" class="form-select" aria-label="Default select example">
<select id="type" v-model="monitor.type" class="form-select">
<option value="http">
HTTP(s)
</option>
@@ -178,6 +178,8 @@
{{ notification.name }}
<a href="#" @click="$refs.notificationDialog.show(notification.id)">{{ $t("Edit") }}</a>
</label>
<span v-if="notification.isDefault == true" class="badge bg-primary ms-2">Default</span>
</div>
<button class="btn btn-primary me-2" type="button" @click="$refs.notificationDialog.show()">
@@ -188,7 +190,7 @@
</div>
</form>
<NotificationDialog ref="notificationDialog" />
<NotificationDialog ref="notificationDialog" @added="addedNotification" />
</div>
</transition>
</template>
@@ -281,7 +283,7 @@ export default {
methods: {
init() {
if (this.isAdd) {
console.log("??????")
this.monitor = {
type: "http",
name: "",
@@ -296,6 +298,12 @@ export default {
dns_resolve_type: "A",
dns_resolve_server: "1.1.1.1",
}
for (let i = 0; i < this.$root.notificationList.length; i++) {
if (this.$root.notificationList[i].isDefault == true) {
this.monitor.notificationIDList[this.$root.notificationList[i].id] = true;
}
}
} else if (this.isEdit) {
this.$root.getSocket().emit("getMonitor", this.$route.params.id, (res) => {
if (res.ok) {
@@ -330,6 +338,12 @@ export default {
})
}
},
// Added a Notification Event
// Enable it if the notification is added in EditMonitor.vue
addedNotification(id) {
this.monitor.notificationIDList[id] = true;
},
},
}
</script>

View File

@@ -120,12 +120,34 @@
</form>
</template>
<h2 class="mt-5 mb-2">{{ $t("Import/Export Backup") }}</h2>
<p>
{{ $t("backupDescription") }} <br />
({{ $t("backupDescription2") }}) <br />
</p>
<div class="input-group mb-3">
<button class="btn btn-outline-primary" @click="downloadBackup">{{ $t("Export") }}</button>
<button type="button" class="btn btn-outline-primary" :disabled="processing" @click="importBackup">
<div v-if="processing" class="spinner-border spinner-border-sm me-1"></div>
{{ $t("Import") }}
</button>
<input id="importBackup" type="file" class="form-control" accept="application/json">
</div>
<div v-if="importAlert" class="alert alert-danger mt-3" style="padding: 6px 16px;">
{{ importAlert }}
</div>
<p><strong>{{ $t("backupDescription3") }}</strong></p>
<h2 class="mt-5 mb-2">{{ $t("Advanced") }}</h2>
<div class="mb-3">
<button v-if="settings.disableAuth" class="btn btn-outline-primary me-1" @click="enableAuth">{{ $t("Enable Auth") }}</button>
<button v-if="! settings.disableAuth" class="btn btn-primary me-1" @click="confirmDisableAuth">{{ $t("Disable Auth") }}</button>
<button v-if="! settings.disableAuth" class="btn btn-danger me-1" @click="$root.logout">{{ $t("Logout") }}</button>
<button class="btn btn-outline-danger me-1" @click="confirmClearStatistics">{{ $t("Clear all statistics") }}</button>
</div>
</template>
</div>
@@ -166,23 +188,76 @@
<NotificationDialog ref="notificationDialog" />
<Confirm ref="confirmDisableAuth" btn-style="btn-danger" :yes-text="$t('I understand, please disable')" :no-text="$t('Leave')" @yes="disableAuth">
<template v-if="$i18n.locale === 'en' ">
<p>Are you sure want to <strong>disable auth</strong>?</p>
<p>It is for <strong>someone who have 3rd-party auth</strong> in front of Uptime Kuma such as Cloudflare Access.</p>
<p>Please use it carefully.</p>
<template v-if="$i18n.locale === 'es-ES' ">
<p>Seguro que deseas <strong>deshabilitar la autenticación</strong>?</p>
<p>Es para <strong>quien implementa autenticación de terceros</strong> ante Uptime Kuma como por ejemplo Cloudflare Access.</p>
<p>Por favor usar con cuidado.</p>
</template>
<template v-if="$i18n.locale === 'zh-HK' ">
<template v-else-if="$i18n.locale === 'zh-HK' ">
<p>你是否確認<strong>取消登入認証</strong></p>
<p>這個功能是設計給已有<strong>第三方認証</strong>的用家例如 Cloudflare Access</p>
<p>請小心使用</p>
</template>
<template v-if="$i18n.locale === 'de-DE' ">
<template v-else-if="$i18n.locale === 'zh-CN' ">
<p>是否确定 <strong>取消登录验证</strong></p>
<p>这是为 <strong>有第三方认证</strong> 的用户提供的功能 Cloudflare Access</p>
<p>请谨慎使用</p>
</template>
<template v-else-if="$i18n.locale === 'de-DE' ">
<p>Bist du sicher das du die <strong>Authentifizierung deaktivieren</strong> möchtest?</p>
<p>Es ist für <strong>jemanden der eine externe Authentifizierung</strong> vor Uptime Kuma geschaltet hat, wie z.B. Cloudflare Access.</p>
<p>Bitte mit Vorsicht nutzen.</p>
</template>
<template v-else-if="$i18n.locale === 'sr' ">
<p>Да ли сте сигурни да желите да <strong>искључите аутентификацију</strong>?</p>
<p>То је за <strong>оне који имају додату аутентификацију</strong> испред Uptime Kuma као на пример Cloudflare Access.</p>
<p>Молим Вас користите ово са пажњом.</p>
</template>
<template v-else-if="$i18n.locale === 'sr-latn' ">
<p>Da li ste sigurni da želite da <strong>isključite autentifikaciju</strong>?</p>
<p>To je za <strong>one koji imaju dodatu autentifikaciju</strong> ispred Uptime Kuma kao na primer Cloudflare Access.</p>
<p>Molim Vas koristite ovo sa pažnjom.</p>
</template>
<template v-else-if="$i18n.locale === 'ko-KR' ">
<p>정말로 <strong>인증 기능을 끌까요</strong>?</p>
<p> 기능은 <strong>Cloudflare Access와 같은 서드파티 인증</strong> Uptime Kuma 앞에 사용자를 위한 기능이에요.</p>
<p>신중하게 사용하세요.</p>
</template>
<template v-else-if="$i18n.locale === 'pl' ">
<p>Czy na pewno chcesz <strong>wyłączyć autoryzację</strong>?</p>
<p>Jest przeznaczony dla <strong>kogoś, kto ma autoryzację zewnętrzną</strong> przed Uptime Kuma, taką jak Cloudflare Access.</p>
<p>Proszę używać ostrożnie.</p>
</template>
<template v-else-if="$i18n.locale === 'et-EE' ">
<p>Kas soovid <strong>lülitada autentimise välja</strong>?</p>
<p>Kastuamiseks <strong>välise autentimispakkujaga</strong>, näiteks Cloudflare Access.</p>
<p>Palun kasuta vastutustundlikult.</p>
</template>
<template v-else-if="$i18n.locale === 'it-IT' ">
<p>Si è certi di voler <strong>disabilitare l'autenticazione</strong>?</p>
<p>È per <strong>chi ha l'autenticazione gestita da terze parti</strong> messa davanti ad Uptime Kuma, ad esempio Cloudflare Access.</p>
<p>Utilizzare con attenzione.</p>
</template>
<!-- English (en) -->
<template v-else>
<p>Are you sure want to <strong>disable auth</strong>?</p>
<p>It is for <strong>someone who have 3rd-party auth</strong> in front of Uptime Kuma such as Cloudflare Access.</p>
<p>Please use it carefully.</p>
</template>
</Confirm>
<Confirm ref="confirmClearStatistics" btn-style="btn-danger" :yes-text="$t('Yes')" :no-text="$t('No')" @yes="clearStatistics">
{{ $t("confirmClearStatisticsMsg") }}
</Confirm>
</div>
</transition>
@@ -221,6 +296,8 @@ export default {
},
loaded: false,
importAlert: null,
processing: false,
}
},
watch: {
@@ -282,6 +359,10 @@ export default {
this.$refs.confirmDisableAuth.show();
},
confirmClearStatistics() {
this.$refs.confirmClearStatistics.show();
},
disableAuth() {
this.settings.disableAuth = true;
this.saveSettings();
@@ -293,6 +374,61 @@ export default {
this.$root.storage().removeItem("token");
},
downloadBackup() {
let time = dayjs().format("YYYY_MM_DD-hh_mm_ss");
let fileName = `Uptime_Kuma_Backup_${time}.json`;
let monitorList = Object.values(this.$root.monitorList);
let exportData = {
version: this.$root.info.version,
notificationList: this.$root.notificationList,
monitorList: monitorList,
}
exportData = JSON.stringify(exportData);
let downloadItem = document.createElement("a");
downloadItem.setAttribute("href", "data:application/json;charset=utf-8," + encodeURI(exportData));
downloadItem.setAttribute("download", fileName);
downloadItem.click();
},
importBackup() {
this.processing = true;
let uploadItem = document.getElementById("importBackup").files;
if (uploadItem.length <= 0) {
this.processing = false;
return this.importAlert = this.$t("alertNoFile")
}
if (uploadItem.item(0).type !== "application/json") {
this.processing = false;
return this.importAlert = this.$t("alertWrongFileType")
}
let fileReader = new FileReader();
fileReader.readAsText(uploadItem.item(0));
fileReader.onload = item => {
this.$root.uploadBackup(item.target.result, (res) => {
this.processing = false;
if (res.ok) {
toast.success(res.msg);
} else {
toast.error(res.msg);
}
})
}
},
clearStatistics() {
this.$root.clearStatistics((res) => {
if (res.ok) {
this.$router.go();
} else {
toast.error(res.msg);
}
})
},
},
}
</script>
@@ -321,6 +457,18 @@ export default {
.btn-check:hover + .btn-outline-primary {
color: #000;
}
#importBackup {
&::file-selector-button {
color: $primary;
background-color: $dark-bg;
}
&:hover:not(:disabled):not([readonly])::file-selector-button {
color: $dark-font-color2;
background-color: $primary;
}
}
}
footer {

View File

@@ -10,26 +10,35 @@
</div>
<p class="mt-3">
Create your admin account
{{ $t("Create your admin account") }}
</p>
<div class="form-floating">
<select id="language" v-model="$i18n.locale" class="form-select">
<option v-for="(lang, i) in $i18n.availableLocales" :key="`Lang${i}`" :value="lang">
{{ $i18n.messages[lang].languageName }}
</option>
</select>
<label for="language" class="form-label">{{ $t("Language") }}</label>
</div>
<div class="form-floating mt-3">
<input id="floatingInput" v-model="username" type="text" class="form-control" placeholder="Username" required>
<label for="floatingInput">Username</label>
<label for="floatingInput">{{ $t("Username") }}</label>
</div>
<div class="form-floating mt-3">
<input id="floatingPassword" v-model="password" type="password" class="form-control" placeholder="Password" required>
<label for="floatingPassword">Password</label>
<label for="floatingPassword">{{ $t("Password") }}</label>
</div>
<div class="form-floating mt-3">
<input id="repeat" v-model="repeatPassword" type="password" class="form-control" placeholder="Repeat Password" required>
<label for="repeat">Repeat Password</label>
<label for="repeat">{{ $t("Repeat Password") }}</label>
</div>
<button class="w-100 btn btn-primary mt-3" type="submit" :disabled="processing">
Create
{{ $t("Create") }}
</button>
</form>
</div>
@@ -49,6 +58,11 @@ export default {
repeatPassword: "",
}
},
watch: {
"$i18n.locale"() {
localStorage.locale = this.$i18n.locale;
},
},
mounted() {
this.$root.getSocket().emit("needSetup", (needSetup) => {
if (! needSetup) {
@@ -71,7 +85,12 @@ export default {
this.$root.toastRes(res)
if (res.ok) {
this.$router.push("/")
this.processing = true;
this.$root.login(this.username, this.password, (res) => {
this.processing = false;
this.$router.push("/")
})
}
})
},