Compare commits

..

712 Commits

Author SHA1 Message Date
Louis Lam
99042e6991 Update to 1.14.0-beta.1 2022-04-03 22:06:39 +08:00
Louis Lam
f54084c888 Update beta release process 2022-04-03 22:06:36 +08:00
Louis Lam
87d3853b8e Merge pull request #1348 from AnnAngela/master
Detect if `fs.rmSync` is available to avoid the runtime deprecation warning
2022-04-02 18:21:12 +08:00
Louis Lam
4738581c66 Update dependencies 2022-04-02 11:34:00 +08:00
Louis Lam
3218a0eee8 Merge remote-tracking branch 'origin/master' 2022-04-02 11:26:24 +08:00
Louis Lam
87ee3c20bd Update proxy password field 2022-04-02 11:25:27 +08:00
Louis Lam
38e6e846bf Refresh sponsor list 2022-04-01 23:22:15 +08:00
Louis Lam
92ab2b12d0 Refresh sponsor list 2022-04-01 17:37:04 +08:00
Louis Lam
04e3394d02 Merge branch 'master' into feature/request-with-http-proxy
# Conflicts:
#	package-lock.json
#	package.json
#	server/database.js
#	src/languages/en.js
#	src/mixins/socket.js
2022-04-01 14:57:35 +08:00
Louis Lam
f6cd2f60ca Merge pull request #1442 from BCsabaEngine/master
fix: update hu lang
2022-04-01 14:46:56 +08:00
Balázs Csaba
53cea7f8d3 fix: update hu lang 2022-03-31 22:25:22 +02:00
Louis Lam
aef7719426 Merge pull request #1435 from LoaderB0T/favico
feat: Favicon is updated based on satus page logo
2022-03-31 21:32:13 +08:00
Louis Lam
514b9fb68a Add remark 2022-03-31 21:30:07 +08:00
Louis Lam
da32a1aa19 Add uk-UA to the languageList 2022-03-31 16:24:28 +08:00
Louis Lam
7a69f9f56f Merge pull request #1438 from Deni7/master
Add ukrainian translation
2022-03-31 16:22:29 +08:00
Louis Lam
c50c20faa4 Minor fix for uk-UA 2022-03-31 16:19:04 +08:00
Louis Lam
cb6eeaef34 Bring connection error bar to the top 2022-03-31 16:15:34 +08:00
Louis Lam
6674005e8b Fix storing cloudflared token while start cloudflared 2022-03-31 15:58:39 +08:00
Denis Stepanov
ee3d7d8b42 Add ukrainian translation 2022-03-30 23:21:09 +03:00
Denis Stepanov
a277cfe9e8 Add ukrainian translation 2022-03-30 23:16:04 +03:00
Denis Stepanov
95b0df0270 Add ukrainian translation 2022-03-30 23:15:28 +03:00
Louis Lam
f02e9c44ec Update to 1.14.0-beta.0 2022-03-30 23:37:23 +08:00
Louis Lam
bb2b5cd6ac Merge pull request #1427 from louislam/cloudflared
Built-in ease-to-use reverse proxy with Cloudflare Tunnel
2022-03-30 20:15:48 +08:00
Louis Lam
b72a2d350f Set cloudflared token from env var or arg 2022-03-30 20:08:26 +08:00
Louis Lam
71be030733 Add package-lock.json and minor words 2022-03-30 18:52:10 +08:00
Janik Schumacher
73b338bba6 feat: Favicon is updated based on satus page logo 2022-03-30 12:09:38 +02:00
Louis Lam
82ea896bbc Improve the workflow of cloudflared 2022-03-30 11:59:49 +08:00
Louis Lam
f1f4b3b377 Add reverse proxy setting page for controlling cloudflared 2022-03-30 01:49:45 +08:00
Louis Lam
a6b52b7ba6 Merge branch 'master' into cloudflared 2022-03-29 17:42:55 +08:00
Louis Lam
b8dea3a823 Merge remote-tracking branch 'origin/master' 2022-03-29 17:39:12 +08:00
Louis Lam
0da6e6b1fb Some improvements 2022-03-29 17:38:48 +08:00
Louis Lam
44fb2a88f2 Add cloudflared socket handler 2022-03-29 14:48:02 +08:00
Louis Lam
623b06e33c Merge pull request #1426 from DX37/translation-ru
update russian translation
2022-03-29 14:14:21 +08:00
Louis Lam
7d3cbff794 [Cloudflared] Install into base docker 2022-03-29 02:24:10 +08:00
DX37
61d0a0abce update russian translation 2022-03-28 21:16:13 +07:00
AnnAngela
7fd5b61bab Inproperly conflict resolving 2022-03-27 21:12:51 +08:00
AnnAngela
96289fe014 Update index.js 2022-03-27 20:56:42 +08:00
AnnAngela
381605aca1 Update update-version.js 2022-03-27 20:55:28 +08:00
AnnAngela
742c6bcaa3 Merge branch 'master' into master 2022-03-27 20:54:24 +08:00
Louis Lam
be88351eb3 Merge pull request #1136 from chakflying/fix/prometheus-on-delete
Fix: Remove prometheus metrics on delete [Test needed]
2022-03-27 11:05:50 +08:00
Louis Lam
34a0b54b93 Merge pull request #1418 from sovushik/patch-11
Update ru-RU.js
2022-03-26 14:11:32 +08:00
sovushik
e11ea7b061 Update ru-RU.js
Add new string for 1.13.1
2022-03-26 10:46:07 +05:00
Louis Lam
12237dec6e Merge remote-tracking branch 'origin/master' 2022-03-26 02:09:25 +08:00
Louis Lam
f6272155af Show page not found for invalid routes 2022-03-26 02:09:12 +08:00
Louis Lam
630b441a2d Merge pull request #1414 from ivanbratovic/croatian-language
Update croatian (hr-HR) translation file
2022-03-25 19:00:06 +08:00
Ivan Bratović
5922771909 Update croatian (hr-HR) translation file
Signed-off-by: Ivan Bratović <ivanbratovic4@gmail.com>
2022-03-25 11:05:51 +01:00
Louis Lam
623d03dc6f Fix release process 2022-03-25 00:03:25 +08:00
Louis Lam
f52e527850 Update to 1.13.1 2022-03-24 23:47:03 +08:00
Louis Lam
28d72fcd08 Fix #1409, slug cannot be empty 2022-03-24 23:43:07 +08:00
Louis Lam
6c7a0ff7d3 Fix release script 2022-03-24 22:44:22 +08:00
Louis Lam
2abdf2efad Update to 1.13.0 2022-03-24 22:21:19 +08:00
Louis Lam
71af08189e Clear useless code 2022-03-24 18:03:31 +08:00
Louis Lam
d32ba7cadd Fix #1318, basic auth is completely disabled if the auth is disabled 2022-03-24 18:02:34 +08:00
Louis Lam
775d1696fa Fix pushover device not working #1114 2022-03-24 12:14:17 +08:00
Louis Lam
7fb16d2f9a Limit the pm2 log size 2022-03-23 11:17:23 +08:00
Louis Lam
40991fbc28 Show reverse proxy guide along with websocket error 2022-03-22 23:46:13 +08:00
Louis Lam
bf20f9d290 Merge remote-tracking branch 'origin/master' 2022-03-22 21:37:18 +08:00
Louis Lam
5fa14161c4 Minor css 2022-03-22 21:37:04 +08:00
Louis Lam
5a2a59250d Merge pull request #1405 from sovushik/patch-9
Update ru-RU.js
2022-03-22 17:17:37 +08:00
Louis Lam
fcee93cbea Merge pull request #1404 from sovushik/patch-8
Update ru-RU.js
2022-03-22 17:16:55 +08:00
Louis Lam
668dffc2c5 Simplify final release 2022-03-22 16:45:07 +08:00
sovushik
210eebe144 Update ru-RU.js
Add some fix for 1.13
2022-03-22 13:00:16 +05:00
sovushik
4b04a9c214 Update ru-RU.js
Add new string for version 1.13
2022-03-22 12:58:38 +05:00
Louis Lam
909618a29a Update to 1.13.0-beta.2 2022-03-22 14:30:54 +08:00
Louis Lam
4a4ffc96dd Fix setup 2022-03-22 12:00:13 +08:00
Louis Lam
3713692bdd Merge remote-tracking branch 'origin/master' 2022-03-22 11:31:01 +08:00
Louis Lam
76f991ecd8 Update beta process 2022-03-22 11:30:45 +08:00
Louis Lam
84dcd81f21 Merge pull request #1400 from MrEddX/bulgarian
Update bg-BG.js
2022-03-22 10:50:59 +08:00
MrEddX
f65d0654a6 Update bg-BG.js
Added new field.
2022-03-21 21:12:15 +02:00
Louis Lam
b0bda9f9d2 Fix beta release script 2022-03-22 01:00:35 +08:00
Louis Lam
ad2130b7b5 [Status Page] Fix monitors are deleted unexpectedly #1399 2022-03-22 00:06:29 +08:00
Louis Lam
4545eec3fe Better sticky monitor list 2022-03-21 23:53:55 +08:00
Louis Lam
3adda48f3a Load the status page list earlier 2022-03-21 15:28:59 +08:00
Louis Lam
cafa61e3af Add beta tag 2022-03-21 14:32:55 +08:00
Louis Lam
58ee071fae Release process for beta 2022-03-21 14:31:29 +08:00
Louis Lam
9173838e1b Merge remote-tracking branch 'origin/master' 2022-03-21 11:50:27 +08:00
Louis Lam
833d9381ff Merge pull request #1393 from burakurer/patch-1
Update tr-TR.js
2022-03-21 11:50:17 +08:00
burakurer
73d904952d Update tr-TR.js
spelling correction "Sağlık Dırımları" => "Sağlık Durumları"
2022-03-20 17:03:32 +03:00
Louis Lam
4e95e9ea51 Add process for beta release 2022-03-20 11:08:33 +08:00
Louis Lam
c22cc4d794 Merge pull request #1385 from jtagcat/patch-1
Update et-EE.js
2022-03-20 10:56:24 +08:00
Louis Lam
8cbdefdc0d Merge pull request #1390 from pemassi/patch-3
Update ko-KR.js
2022-03-20 10:55:51 +08:00
Kyungyoon Kim
2f5beefa37 Update ko-KR.js 2022-03-19 03:19:02 -06:00
jtagcat
dae5ff690a Update et-EE.js 2022-03-18 13:56:45 +00:00
Louis Lam
fb9a206542 [Status Page] Fix - show no status page 2022-03-18 21:47:14 +08:00
jtagcat
dc3da45dd6 Update et-EE.js 2022-03-18 11:27:33 +00:00
Louis Lam
82049a2387 Merge pull request #863 from louislam/restructure-status-page
Restructure status page core implementation
2022-03-18 18:07:15 +08:00
Louis Lam
d7a839aa52 [Status Page] Fix reset entry page 2022-03-18 17:57:08 +08:00
Louis Lam
aef0a66205 [Status Page] Simplify show tags logic 2022-03-18 17:56:46 +08:00
Louis Lam
37be7df9b0 [Status Page] Delete status page 2022-03-18 15:19:52 +08:00
Louis Lam
243fab5f26 Rollback vite to 2.6.x (Not sure, but sometimes vue routes are no longer response during dev randomly) 2022-03-18 15:02:49 +08:00
Louis Lam
8d981c8f0b [Status Page] Fix migration and unpin incident 2022-03-18 14:14:22 +08:00
Louis Lam
220e46bc83 [Status Page] Fix theme bug 2022-03-18 12:57:37 +08:00
Louis Lam
59cdacc052 [Status Page] Enable Edit Mode only if the token is presented 2022-03-18 12:39:48 +08:00
Louis Lam
00738edbe7 [Status Page] Add ?edit 2022-03-18 00:00:56 +08:00
Louis Lam
27bfae67af [Status Page] Add a new status page 2022-03-17 23:38:43 +08:00
Louis Lam
719a136d1e [Status Page] Improved entry page 2022-03-17 22:44:47 +08:00
Louis Lam
502c7f87e7 [Status Page] Listing: Better loading effect 2022-03-17 19:07:05 +08:00
Louis Lam
78a732409b [Status Page] Fix translations 2022-03-17 18:56:59 +08:00
Louis Lam
c0c6419980 [Status Page] align icon and title using flexbox 2022-03-17 17:07:23 +08:00
Louis Lam
5474368263 Update vite to 2.8.6 2022-03-17 16:56:25 +08:00
Louis Lam
e87cdf4d09 [Status Page] wip, upload logo and status page listing 2022-03-17 16:42:26 +08:00
Louis Lam
bb1c951a96 Update node.js from 14 to 16 2022-03-17 13:04:43 +08:00
Louis Lam
1033ca5cf4 [Status Page] wip, combine api, add status_page_id into group and incident tables 2022-03-16 15:38:10 +08:00
Louis Lam
18ec42b060 [Status Page] wip 2022-03-16 14:14:47 +08:00
Louis Lam
7c7dbf68c1 [Status Page] wip, sidebar for editor 2022-03-15 12:00:29 +08:00
Louis Lam
3e96504813 Update denpendencies 2022-03-13 17:14:57 +08:00
Louis Lam
d765b1c57a Merge branch 'master' into restructure-status-page
# Conflicts:
#	src/pages/StatusPage.vue
2022-03-12 15:50:42 +08:00
Louis Lam
5f778b9763 Merge pull request #835 from willianrod/feat/add-favicon-badges
Add badges to favicon
2022-03-12 15:39:18 +08:00
Louis Lam
c68f7944e3 [Favicon] minor 2022-03-12 15:31:01 +08:00
Louis Lam
a9efdabcec [Favicon] Prevent error when no heartbeat 2022-03-12 15:30:02 +08:00
Louis Lam
b9dfcd1291 [Favicon] Code refactoring 2022-03-12 15:10:45 +08:00
Louis Lam
04d93c2747 Merge branch 'master' into willianrod_feat/add-favicon-badges
# Conflicts:
#	package-lock.json
#	src/mixins/socket.js
2022-03-12 11:17:32 +08:00
Louis Lam
c65d771fad Merge pull request #1358 from BCsabaEngine/master
fix: .hu lang
2022-03-12 10:38:37 +08:00
Louis Lam
3f8a396090 Merge pull request #1362 from MrEddX/bulgarian
Bulgarian
2022-03-12 10:37:46 +08:00
Louis Lam
9681957adf Merge pull request #1366 from deanilvincent/update-version-of-check-password-strength
check-password-strength new version update 2.0.5
2022-03-12 10:25:06 +08:00
Mark Vicente
95a2c967c6 check-password-strength new version 2.0.5 that support additional symbols/special characters referenced from owasp list. 2022-03-11 23:48:35 +08:00
Louis Lam
50d6e888c2 [new status page] wip 2022-03-10 21:34:30 +08:00
Louis Lam
ae14ad5a84 Add a word "Status Pages" 2022-03-09 22:14:07 +08:00
MrEddX
edd9202de9 Update bg-BG.js
Translation fix.
2022-03-08 22:14:07 +02:00
MrEddX
a97d2a5498 Update bg-BG.js
Added new fields.
2022-03-08 22:10:17 +02:00
Louis Lam
72ce28a541 Migrate status page table 2022-03-08 14:33:35 +08:00
Louis Lam
1e2a8453c6 Merge branch 'master' into restructure-status-page 2022-03-08 14:21:04 +08:00
Louis Lam
1fa4a16663 Check beta release 2022-03-07 16:24:24 +08:00
Louis Lam
6a57c443fd Set telegram as the default notification type 2022-03-07 15:52:17 +08:00
Uğur Erkan
8078d0618d Add socks proxy support to proxy feature
- Socks proxy support implemented.
- Monitor proxy agent create flow refactored
  and moved under proxy class.

Thanks for suggestion @thomasleveil
2022-03-06 19:34:51 +03:00
Uğur Erkan
9e27acb511 Add socks proxy agent 2022-03-06 19:34:51 +03:00
Uğur Erkan
78d76512ba Add http and https proxy feature
Added new proxy feature based on http and https proxy agents.
Proxy feature works like notifications, there is many proxy
could be related one proxy entry.

Supported features
- Proxies can activate and disable in bulk
- Proxies auto enabled by default for new monitors
- Proxies could be applied in bulk to current monitors
- Both authenticated and anonymous proxies supported
- Export and import support for proxies
2022-03-06 19:34:49 +03:00
Uğur Erkan
2cc7a990ff Add http and https agents 2022-03-06 19:29:28 +03:00
Balázs Csaba
157f0de61a fix: .hu lang 2022-03-05 21:24:50 +01:00
Louis Lam
88c3d952d3 Improve settings page's UI/UX on mobile 2022-03-04 23:20:42 +08:00
Louis Lam
e3a0eaf6af Sort notification types in case-insensitive 2022-03-04 21:48:35 +08:00
Louis Lam
8bbf55777e Merge pull request #1205 from arjunkomath/master
Add notification provider - Push
2022-03-04 21:39:59 +08:00
Louis Lam
c0e0698c21 Merge pull request #1225 from Computroniks/fix-checkbox-css
Fixed dark mode checkbox
2022-03-04 14:35:15 +08:00
Louis Lam
14d8095f12 Merge pull request #1228 from Arubinu/alerta
Alerta Notification Service
2022-03-04 14:19:53 +08:00
Louis Lam
fa490d0bf1 [Alerta] Handle general message 2022-03-04 14:13:44 +08:00
Louis Lam
c52c8a4206 Merge branch 'master' into alerta
# Conflicts:
#	server/notification.js
#	src/components/notifications/index.js
#	src/languages/en.js
2022-03-04 14:10:37 +08:00
Louis Lam
9789d8cde8 Merge branch 'master' into alerta 2022-03-04 14:09:01 +08:00
Louis Lam
ccb3d85a48 Merge pull request #1157 from zackelia/master
Implement gorush notifications
2022-03-03 22:03:09 +08:00
Louis Lam
333505b039 Merge remote-tracking branch 'origin/master' 2022-03-03 20:49:13 +08:00
Louis Lam
602da565eb Sort notification types 2022-03-03 20:49:00 +08:00
Louis Lam
b62d94184a Merge branch 'master' into restructure-status-page 2022-03-03 17:09:15 +08:00
Louis Lam
e0175d0010 Delete stale-bot.yml, no idea why it deleted some feature request 2022-03-03 10:21:34 +08:00
Louis Lam
3246055696 Merge pull request #1350 from deluxghost/patch-1
Update zh-CN.js
2022-03-02 23:20:47 +08:00
deluxghost
b3a690f3b1 Update zh-CN.js 2022-03-02 23:12:30 +08:00
Louis Lam
7bc8c447cd Merge branch 'MikMuellerDev_master'
# Conflicts:
#	src/languages/de-DE.js
2022-03-02 17:17:15 +08:00
Louis Lam
69ff6831ab Merge pull request #1311 from deluxghost/update-zh-cn
Update zh-CN translations
2022-03-02 16:58:54 +08:00
AnnAngela
88a798704b Update fs-rmSync.js 2022-03-02 16:10:14 +08:00
AnnAngela-work
783173fd1f Add a helper function 2022-03-02 15:48:08 +08:00
DX
0dba06e48b Update zh-CN translations 2022-03-02 15:34:11 +08:00
AnnAngela-work
281fe365c0 Mark the version as 1.11.4 in package-lock.json 2022-03-02 15:23:08 +08:00
Louis Lam
8e7c0a6163 Update pull request rules 2022-03-02 14:25:37 +08:00
Louis Lam
0671e4ea2b Merge remote-tracking branch 'AnnAngela/master'
# Conflicts:
#	src/languages/zh-CN.js
2022-03-02 13:43:34 +08:00
Louis Lam
cd8eaef903 Merge pull request #1187 from pfandie/translations-de
Updates some DE translations
2022-03-02 13:39:19 +08:00
Louis Lam
51f5c009e3 Merge remote-tracking branch 'PrikolMen/patch-1'
# Conflicts:
#	src/languages/ru-RU.js
2022-03-02 13:37:21 +08:00
Louis Lam
3bf62c9ceb Merge branch 'patch-7-ru'
# Conflicts:
#	src/languages/ru-RU.js
2022-03-02 13:33:37 +08:00
Louis Lam
7b11539cff Merge branch 'patch-62'
# Conflicts:
#	src/languages/ru-RU.js
2022-03-02 13:31:43 +08:00
PrikolMen:-b
b4a3d68356 More correct Russian translation
I tried to fix most of the shortcomings of the Russian translation...
2022-02-28 14:55:23 +04:00
Louis Lam
b31af8a15c update to 1.12.1 2022-02-26 17:05:13 +08:00
Louis Lam
60f67ccb35 Revert commit: a6fd626f 2022-02-26 16:57:13 +08:00
Louis Lam
81a9807a0a Update release procedures 2022-02-26 16:03:43 +08:00
Louis Lam
3681934d05 Update Apprise to 0.9.7 2022-02-26 15:57:26 +08:00
Louis Lam
d5d63474d8 update to 1.12.0 2022-02-26 15:41:32 +08:00
Louis Lam
a6fd626fb8 Locked Russian language, ask Putin to stop the war and unlock it 2022-02-26 14:56:57 +08:00
Louis Lam
3a5b413af4 Update axios to 0.26.0 due to vulnerability 2022-02-26 14:36:38 +08:00
Louis Lam
595cd93220 Check invalid interval 2022-02-24 15:11:17 +08:00
Louis Lam
e12c1511db Merge pull request #1330 from BCsabaEngine/master
fix: hu lang
2022-02-23 22:36:05 +08:00
Balázs Csaba
f3112c0b85 fix: hu lang 2022-02-23 09:35:56 +01:00
Louis Lam
af07850ddf Merge pull request #1287 from sovushik/patch-5
Update ru-RU.js
2022-02-21 15:12:27 +08:00
Louis Lam
211b44269c Do not close feature-request 2022-02-21 11:48:03 +08:00
Louis Lam
7638b73645 Fix #1300 2022-02-15 23:30:07 +08:00
Mik Mueller
a997f8e4f9 Update de-DE.js 2022-02-15 12:58:02 +01:00
Mik Mueller
09dbb143ea Merge branch 'louislam:master' into master 2022-02-15 12:51:21 +01:00
Hans Mayer
f19e983818 Merge remote-tracking branch 'origin/master' into translations-de 2022-02-14 14:39:21 +01:00
Louis Lam
d0ed99a310 Merge pull request #1298 from ananthkamath/master
Fix mattermost couldn't find channel issue
2022-02-13 23:56:12 +08:00
Ananth Kamath
258d93be72 Fix mattermost couldn't find channel issue 2022-02-13 21:17:02 +05:30
Louis Lam
986ddd92ff Merge pull request #1198 from Buchtic/master
CSY translation
2022-02-13 14:11:00 +08:00
AnnAngela
c75c6c5640 Update zh-CN.js
Update DingDing and AliyunSms setting dialogs for better translations and document links
2022-02-11 22:50:46 +08:00
AnnAngela
5aed36b470 Update zh-CN
----
关于 smtpDkimKeySelector:
Google Workspace 的帮助里用词为
[前缀选择器](https://annangela.page.link/smtpDkimKeySelector)
2022-02-10 22:45:24 +08:00
sovushik
76b9fb967f Update ru-RU.js
Add new string
2022-02-09 21:37:45 +05:00
sovushik
b58120d258 Update ru-RU.js
Correct some words on Russian
2022-02-09 21:26:47 +05:00
sovushik
79f99ce215 Update ru-RU.js
Add new string
2022-02-09 21:19:00 +05:00
Louis Lam
e7e30bf497 update to 1.11.4 2022-02-09 21:54:33 +08:00
Louis Lam
efaa55ad1f Merge pull request #1269 from holao09/master
Update Vietnamese language
2022-02-09 21:42:02 +08:00
Louis Lam
32a898bee5 Merge pull request #1270 from rovast/master
Update zh-CN  translation for setting module.
2022-02-09 21:36:07 +08:00
Louis Lam
561a0a3c9a Merge pull request #1278 from jamesmacwhite/disable-auth-lang
Fix minor typos on disable auth warning
2022-02-09 21:35:37 +08:00
Việt Nguyễn
daac9ddffc Update Vietnamese language
Cập nhật một số thông tin tiếng Việt
2022-02-09 16:17:10 +07:00
James White
6bd2ee8c69 Fix minor typos on disable auth warning 2022-02-08 21:53:15 +00:00
rovast
45dca072b2 Update zh-CN translation for setting module. 2022-02-07 15:28:21 +08:00
Mik Mueller
7d8b72c6c0 Merge branch 'louislam:master' into master 2022-02-06 10:57:10 +01:00
Hans Mayer
40cc885eb8 resolve conflict after update state 2022-02-05 11:48:48 +01:00
Louis Lam
742ad083e5 Fix security vulnerabilities 2022-02-03 12:26:50 +08:00
Louis Lam
27f4f5ee0b Merge remote-tracking branch 'origin/master' 2022-02-03 12:20:29 +08:00
Louis Lam
41f1686147 Fix security vulnerabilities 2022-02-03 12:20:15 +08:00
Louis Lam
faab1ead92 Merge pull request #1251 from dave9123/patch-2
id-ID.js - Fixed the grammar issue
2022-02-03 12:11:31 +08:00
Mik Mueller
f1007ad42f Update de-DE.js 2022-02-02 22:59:13 +01:00
Mik Mueller
dd28ecaa2d Update de-DE.js 2022-02-02 22:57:02 +01:00
Mik Mueller
ffa585376d Merge branch 'louislam:master' into master 2022-02-02 22:54:23 +01:00
dave9123
c1c1e2ba5b Fixed the grammar issue
Here's my pull request
2022-02-02 10:13:55 +07:00
Louis Lam
2f7e24191a Merge pull request #1237 from dave9123/patch-1
[id-ID] Fixed some grammar error
2022-02-02 00:31:50 +08:00
vfaergestad
0fce1b4b9b Update nb-NO.js (#1232)
Improved and finished the translation.
2022-02-02 00:31:00 +08:00
Mik Mueller
11c2e86bfe Update src/languages/de-DE.js
Co-authored-by: Alf <62615304+Alf-Melmac@users.noreply.github.com>
2022-02-01 08:13:30 +01:00
Mik Mueller
1bbf17f3da Update src/languages/de-DE.js
Co-authored-by: Alf <62615304+Alf-Melmac@users.noreply.github.com>
2022-02-01 08:13:23 +01:00
Mik Mueller
39f8b30b36 Update src/languages/de-DE.js
Co-authored-by: Alf <62615304+Alf-Melmac@users.noreply.github.com>
2022-02-01 08:13:13 +01:00
Mik Mueller
ffb2c2996b Update src/languages/de-DE.js
Co-authored-by: Alf <62615304+Alf-Melmac@users.noreply.github.com>
2022-02-01 08:12:42 +01:00
dave9123
65896ed035 Fixed some grammar error
I fixed at some part of the text.
2022-01-31 08:01:45 +07:00
Mik Mueller
b13b20bd95 improve certain German words and phrases, improve grammer in README.md 2022-01-30 00:17:25 +01:00
Alvin Pergens
8febff9282 fix comments 2022-01-28 15:35:33 +01:00
Alvin Pergens
90f2497548 change data for Alerta 2022-01-28 15:14:34 +01:00
Phuong Nguyen Minh
a9df7b4a14 Update vi.js (#1226)
* update vi.js
2022-01-28 21:23:37 +08:00
Alvin Pergens
cefe43800f add alerta service 2022-01-27 20:54:04 +01:00
Computroniks
eaf370637e Fixed dark mode checkbox
The border colour of the checkbox has been changed to make it more
visible to the user when the dark mode is in use.

Signed-off-by: Computroniks <mnickson@sidingsmedia.com>
2022-01-27 17:40:03 +00:00
Arjun Komath
23796723dd Address code review
Add missing comma

Co-authored-by: Adam Stachowicz <saibamenppl@gmail.com>
2022-01-21 20:42:08 +11:00
Arjun Komath
51b7a2badb remove log 2022-01-21 07:43:14 +00:00
Arjun Komath
74c584f544 Add Push by Techulus 2022-01-21 07:42:03 +00:00
Louis Lam
c3c4db52ec Merge pull request #1184 from Khord/patch-1
Rename 2FA/TOTP field ID for password manager filling compatibility
2022-01-20 15:18:42 +08:00
Louis Lam
aba6cb2c52 Merge pull request #1169 from jbenguira/patch-1
Fixed #1024
2022-01-19 14:41:15 +08:00
Louis Lam
ff0e85737f Merge pull request #1182 from sovushik/patch-4
Update ru-RU.js
2022-01-19 14:37:08 +08:00
Buchtič
4713820da7 first csy translation 2022-01-18 14:44:11 +01:00
Buchtič
a99e87c02c cs-CZ 2022-01-18 08:50:11 +01:00
Buchtič
3f8ca82434 cs-CZ translation 2022-01-18 08:48:39 +01:00
Buchtič
60f1eb7b45 new cs-CZ.js 2022-01-17 18:42:32 +01:00
Louis
55a593f75d Merge remote-tracking branch 'origin/master' 2022-01-18 01:24:28 +08:00
Louis
a0d51a15cf Fix security vulnerabilities 2022-01-18 01:24:07 +08:00
Louis Lam
5a08b42e4f Merge pull request #1194 from Rayzggz/master
Update zh-CN.js
2022-01-18 01:17:33 +08:00
Louis
6961af005e eslint 2022-01-18 01:12:25 +08:00
Roy Feng
847a19afc1 Update zh-CN.js 2022-01-17 15:03:39 +08:00
Louis Lam
7532e7fd3e Merge pull request #1192 from drodmantras/master
Update sl-SI.js
2022-01-17 14:18:27 +08:00
Erik
63a3704836 Update sl-SI.js 2022-01-16 12:29:00 +01:00
Hans Mayer
3e87eb596f change wording, according to PR suggestions 2022-01-15 12:25:17 +01:00
Hans Mayer
c679613f7e Updates some DE translations, fix typo in resolverserverDescription, removes some duplicates in languages 2022-01-14 19:06:21 +01:00
Louis
bd8fa17887 Merge remote-tracking branch 'origin/master' 2022-01-15 01:25:50 +08:00
Louis
d1a99b0a22 Check Node.js version, better error message 2022-01-15 01:25:28 +08:00
Joseph Benguira
3b9fac2942 Update server/prometheus.js
removed useless spaces

Co-authored-by: Adam Stachowicz <saibamenppl@gmail.com>
2022-01-14 08:51:45 +02:00
Dylan Khor
812e80030b revert Token because of language file 2022-01-13 23:43:57 -05:00
Dylan Khor
b89efa49aa retain Token verbiage in display name
since "Token" is used in several places in the 2FA setup screen
2022-01-13 16:58:46 -05:00
Dylan Khor
6490ef3787 rename 2fa input element id and display name 2022-01-13 16:46:09 -05:00
sovushik
329c8cbc2d Update ru-RU.js
Add new string
2022-01-13 22:07:21 +05:00
Louis Lam
2bf9764cec Merge pull request #1175 from sovushik/patch-3
Update ru-RU.js
2022-01-13 13:18:23 +08:00
Louis Lam
c116754360 Merge pull request #1171 from Saibamen/patch-1
Update pl.js
2022-01-13 13:17:06 +08:00
Louis Lam
2c7a701c84 Merge remote-tracking branch 'origin/master' 2022-01-13 11:24:21 +08:00
Louis Lam
fd1fce0143 Update stylelint from 13.13.x to 14.2.x 2022-01-13 11:15:38 +08:00
sovushik
52e0d74a1e Update ru-RU.js
Add new string
2022-01-12 20:49:41 +05:00
Adam Stachowicz
23532aaafe Update pl.js
Addendum to ab61acab63
2022-01-12 12:26:23 +01:00
Louis Lam
ab61acab63 Merge pull request #1170 from NixNotCastey/update-pl
Update pl.js
2022-01-12 17:39:12 +08:00
Łukasz Szczepański
06aab3dee8 Added missing text and fix typos 2022-01-12 09:48:37 +01:00
Joseph Benguira
13acdd4c65 Fix for issue in logs
This fix address the issue described here: https://github.com/louislam/uptime-kuma/issues/1024
2022-01-12 10:12:12 +02:00
Louis Lam
fe0bce268d Merge pull request #1164 from ledeuns/master
Fix comment (FreeBSD->*BSD)
2022-01-12 01:49:57 +08:00
Louis
ed64853125 Keep FBSD, BSD for ping only 2022-01-12 01:44:01 +08:00
Denis
0f822d3b2a FBSD does not exists anymore 2022-01-11 13:42:51 +01:00
Denis
6bda5c6329 update comment 2022-01-11 13:39:45 +01:00
Denis Fondras
44bc98a453 Merge branch 'louislam:master' into master 2022-01-11 13:37:54 +01:00
Louis
f9751d0c01 Fix FBSD to BSD https://github.com/louislam/uptime-kuma/pull/1155#issuecomment-1009544236 2022-01-11 19:15:28 +08:00
Denis
53df9a36e3 reintroduce exports.FBSD 2022-01-11 10:07:00 +01:00
Louis Lam
ccfd04a431 Merge pull request #1155 from ledeuns/master
ping path is common to all BSDs
2022-01-10 18:20:06 +08:00
Louis Lam
9324137123 Merge pull request #1151 from sovushik/patch-2
Update ru-RU.js
2022-01-10 18:19:04 +08:00
Louis Lam
9f063cf477 Merge branch 'master' into patch-2 2022-01-10 17:56:47 +08:00
Louis Lam
b83c896d0c Merge pull request #1150 from sovushik/patch-1
Update ru-RU.js
2022-01-10 17:55:46 +08:00
Louis Lam
aa37383065 Merge pull request #1154 from jamesmacwhite/pull-request-guidelines
Update PULL_REQUEST_TEMPLATE.md
2022-01-10 17:54:55 +08:00
Louis Lam
5e2c39eb4b Merge pull request #1144 from alex3025/master
Fixed italian (it-IT) translations
2022-01-10 17:54:22 +08:00
Louis Lam
2a1f011f05 Merge pull request #1153 from drodmantras/master
Update sl-SI.js
2022-01-10 17:53:03 +08:00
Zack Elia
ea43422ccf Implement gorush notifications 2022-01-09 12:05:11 -05:00
Denis
8063449f49 ping path is common to all BSDs 2022-01-09 17:27:24 +01:00
James White
b6ad4c845a Update PULL_REQUEST_TEMPLATE.md
Correct checklist item and make the UI type of change consistent with all others.
2022-01-09 15:31:26 +00:00
Erik
cdcdf377ec Update sl-SI.js
Small langue updates
2022-01-09 11:39:05 +01:00
sovushik
30a345d8b6 Update ru-RU.js
Updated language files and added new lines
2022-01-08 23:00:18 +05:00
sovushik
83d60fea29 Update ru-RU.js
1. Updated the language lines (in the correct declension)
2. Added new lines for the "Create Incident" functionality
2022-01-08 22:46:05 +05:00
Matteo D
2304c53c8d Fixed some translations
And made them more user-friendly.
2022-01-08 13:28:48 +01:00
Nelson Chan
1bbd744d02 Chore: Improve syntax 2022-01-07 14:29:42 +08:00
Nelson Chan
2e0e35a1ee Fix: Fix typo 2022-01-07 12:34:01 +08:00
Nelson Chan
1e92487f30 Chore: Remove onDelete as unused 2022-01-07 12:28:08 +08:00
Nelson Chan
edd2534a1b Fix: Clear metrics also on stop and edit 2022-01-07 12:26:26 +08:00
Nelson Chan
f6ef390c76 Fix: Remove Prom. metrics on delete monitor 2022-01-07 12:04:57 +08:00
Louis Lam
d4b86dc472 Merge pull request #1133 from thomasleveil/patch-1
fix `TypeError: Cannot read property 'id' of null`
2022-01-06 16:19:36 +08:00
Thomas LÉVEIL
46fa6a56fa fix TypeError: Cannot read property 'id' of null
when testing a Google Chat notification

see https://github.com/louislam/uptime-kuma/issues/1126#issuecomment-1006343423
2022-01-06 08:48:12 +01:00
Louis Lam
ec5037f30d update to 1.11.3 2022-01-06 14:51:20 +08:00
Louis Lam
81a194d826 Merge remote-tracking branch 'origin/master' 2022-01-06 14:47:21 +08:00
Louis Lam
64b3e04d3f Fix #1129 2022-01-06 14:34:45 +08:00
Louis Lam
4ee829ab25 Merge pull request #1130 from bilipp/google-chat-notifications
Fix Google Chat notification type mismatch
2022-01-06 13:05:55 +08:00
Philipp Bischoff
bcc3cec7d6 extract translation for notification type 2022-01-05 23:57:40 +01:00
Philipp Bischoff
f8c5015e3f fix google chat type mismatch 2022-01-05 23:44:14 +01:00
Louis Lam
8f3ec33591 update to 1.11.2 2022-01-05 16:40:50 +08:00
Louis Lam
c5fe3a64c2 Merge remote-tracking branch 'origin/master' 2022-01-05 16:26:03 +08:00
Louis Lam
2a1456cfd0 Merge pull request #1124 from MrEddX/bulgarian
Update bg-BG.js
2022-01-05 14:28:39 +08:00
MrEddX
69dfc0c0d2 Update bg-BG.js
Fixed some typos.
2022-01-05 08:15:34 +02:00
Louis Lam
6d11289257 Merge pull request #1095 from LeslieLeung/add-wecom-notification
feat(*): support WeCom notification
2022-01-04 22:50:30 +08:00
Leslie Leung
590859a95b Merge branch 'master' into add-wecom-notification 2022-01-03 21:43:56 +08:00
Louis Lam
f9c0ff1841 Merge pull request #1109 from iomataani/master
Updated translation
2022-01-03 20:05:46 +08:00
Louis Lam
a8566acbaa Merge pull request #1116 from Minvinea/master
Update translation FR
2022-01-03 20:04:33 +08:00
Minvinea
4b07ec23fe Update 2022-01-03 00:27:51 +01:00
Ioma Taani
0e50b71290 Merge branch 'louislam:master' into master 2021-12-31 09:09:53 +01:00
Louis Lam
390b50353f Merge pull request #1106 from MrEddX/bulgarian
Update bg-BG.js
2021-12-30 19:10:42 +08:00
MrEddX
d7cb4fa331 Update bg-BG.js
- Updated Bulgarian language file
2021-12-30 08:12:25 +02:00
Louis Lam
e18d4b6ad0 Merge pull request #1045 from bilipp/google-chat-notifications
Add support for Google Chat Notifications
2021-12-30 00:16:34 +08:00
Louis Lam
f6fc3737fc Change name from "Google Chat" to Google Chat (Google Workspace only) 2021-12-30 00:10:54 +08:00
Louis Lam
4005856ba6 run build dist when building docker image 2021-12-27 19:09:51 +08:00
Louis Lam
72a59ce7a4 add status page table 2021-12-27 18:54:48 +08:00
LeslieLeung
40b70277c7 feat(*): support WeCom notification 2021-12-26 13:11:42 +08:00
Ioma Taani
a2bc74c4fd updated 2021-12-24 12:02:50 +01:00
Louis Lam
a48176bd48 Merge pull request #1080 from chakflying/feat/smtp-dkim
Feat: Add SMTP DKIM settings
2021-12-22 20:08:29 +08:00
Louis Lam
7cfc5c64b7 Missing a full stop 2021-12-22 13:49:57 +08:00
Nelson Chan
624cd862a5 Feat: Expose SMTP DKIM settings 2021-12-19 13:30:53 +08:00
Louis Lam
0ca68f791f Merge pull request #1060 from drodmantras/master
Added sl-SI language
2021-12-18 00:58:56 +08:00
Louis Lam
6127eab517 Merge pull request #1071 from MrEddX/bulgarian
Update bg-BG.js
2021-12-18 00:57:31 +08:00
MrEddX
0de7fb69f6 Update bg-BG.js
Added new fields.
Translated new fields.
2021-12-17 12:29:53 +02:00
Louis Lam
a42932a43e Simulate Chrome's request Accept header. Better handling of #1067 2021-12-16 15:09:10 +08:00
Philipp Bischoff
a6072a0e30 google chat: only show offline message in notification when service went down 2021-12-15 13:40:21 +01:00
Ivan Bratović
475a466c7e Add attribute to basicauth-pass to prevent browsers from autocompleting (#1063) 2021-12-15 18:18:30 +08:00
Louis Lam
5bc68d7f3b Update README.md 2021-12-15 02:50:45 +08:00
Louis Lam
000703837b Update README.md 2021-12-15 02:46:13 +08:00
Erik
b10cecb362 Added sl-SI language
Added sl-SI language
2021-12-14 17:59:26 +01:00
Louis Lam
6d6cb2ad49 Merge pull request #1047 from dhfhfk/master
Update Ko-KR.js
2021-12-14 17:28:38 +08:00
Louis Lam
cb76801b85 Merge pull request #1049 from Ponkhy/german-language
Updated de-De.js
2021-12-14 13:17:22 +08:00
Ponkhy
aa92727a61 Updated de-De 2021-12-12 21:52:51 +01:00
dhfhfk
56dfa05642 Update Ko-KR.js 2021-12-12 18:56:10 +09:00
Philipp Bischoff
8ad6bd31d4 Revert "order notification types by name"
This reverts commit 8398466860.
2021-12-12 00:08:33 +01:00
Philipp Bischoff
a71569379e add missing import 2021-12-12 00:01:12 +01:00
Philipp Bischoff
8398466860 order notification types by name 2021-12-11 23:50:03 +01:00
Philipp Bischoff
8050cb8e99 implement google chat notification type 2021-12-11 23:43:12 +01:00
Louis Lam
71492aeb3a Merge remote-tracking branch 'origin/master' 2021-12-11 20:59:45 +08:00
Louis Lam
5ee5ea909d Add Github Action: close-incorrect-issue.yml 2021-12-11 20:59:31 +08:00
Louis Lam
a09b97f778 Merge pull request #1032 from titiscan/patch-1
Update fr-FR.js
2021-12-11 17:31:13 +08:00
Louis Lam
e0a08e6b5d Merge pull request #1038 from iomataani/master
Updated italian translation
2021-12-11 17:30:44 +08:00
Louis Lam
6f5cbbdf69 Merge pull request #1018 from Lrss/master
Update Danish translation
2021-12-11 17:29:46 +08:00
Louis Lam
34ee342d3e eslint 2021-12-11 17:20:51 +08:00
Ioma Taani
f793aa5264 better translations 2021-12-10 09:26:55 +01:00
Ioma Taani
728485d686 updated translations 2021-12-10 09:08:01 +01:00
titiscan
cb3429d3c7 Update fr-FR.js
typo fix
2021-12-09 17:26:05 +01:00
Louis Lam
807519d07d Merge branch 'master' into restructure-status-page 2021-12-09 21:46:35 +08:00
Louis Lam
0d69b4426e Merge pull request #1017 from SiderealArt/patch-1
update zh-TW translation
2021-12-09 21:32:10 +08:00
Louis Lam
8bb8b0a53c update to 1.11.1 2021-12-09 01:23:49 +08:00
Louis Lam
a4841eb8aa Fix #1016, .at() is not support in Safari 2021-12-09 01:19:09 +08:00
Lars Sørensen
2ef2a42e87 Fixed string enclosure as suggested by update-language-files 2021-12-08 11:35:55 +01:00
Lars Sørensen
9473cd6919 Update da-DK.js translation 2021-12-08 11:14:10 +01:00
SiderealArt
74f18a2b3f update zh-TW translation 2021-12-08 17:23:13 +08:00
Louis Lam
f9cd0eb084 fix upload-artifacts 2021-12-08 15:35:44 +08:00
Louis Lam
6a845bd937 Merge remote-tracking branch 'origin/master' 2021-12-08 15:26:19 +08:00
Louis Lam
c91f517121 Update README.md 2021-12-08 15:25:43 +08:00
Louis Lam
7899707582 update to 1.11.0 2021-12-08 15:17:37 +08:00
Louis Lam
12215af2f4 Merge pull request #1014 from iooner/patch-2
Fix typos
2021-12-08 15:04:33 +08:00
Louis Lam
d4bfe57b79 minor: improve formatting 2021-12-08 15:04:18 +08:00
Louis Lam
dcc91d6c72 Fix #922 2021-12-08 14:59:59 +08:00
iooner
a041a7964a Fix typos 2021-12-07 14:23:13 +01:00
Louis Lam
76611ecaca Merge pull request #1011 from iomataani/master
Updated translation
2021-12-07 19:26:22 +08:00
Ioma Taani
f802154456 updated 2021-12-06 11:33:39 +01:00
Ioma Taani
9fb461976d Merge branch 'louislam:master' into master 2021-12-06 11:32:24 +01:00
Louis Lam
c8e364911f Delete close-issue.yml, no idea why not working 2021-12-06 17:46:46 +08:00
Louis Lam
88bc08e7b7 Update close-issue.yml 2021-12-06 17:36:18 +08:00
Louis Lam
03aeab0421 close issues that don't follow the issue template
Copy from axios
2021-12-06 17:31:25 +08:00
Louis Lam
f331f1a63e Merge pull request #873 from Saibamen/fix_871
Fix Telegram Bot Token displayed in notification setup view
2021-12-05 18:04:54 +08:00
Louis
d645e29455 mask telegram api url with asterisk 2021-12-05 17:40:13 +08:00
Louis Lam
b4507f9706 Merge pull request #992 from jlbrt/stackfield-notifications
Add support for Stackfield notifications
2021-12-05 16:45:30 +08:00
Louis Lam
fc6d0d1fca Merge pull request #1000 from BCsabaEngine/master
NPM update and (boring) HU lang
2021-12-05 16:30:31 +08:00
Louis
b62f1475ee 🐍 2021-12-05 16:28:59 +08:00
Louis
d47d8517a8 update apprise to 0.9.6 2021-12-05 16:05:52 +08:00
Ioma Taani
19d2db6c8c better translation 2021-12-04 11:19:56 +01:00
Balázs Csaba
5a8162747c Upgrade qrcode to 1.5.0 2021-12-03 22:47:40 +01:00
Balázs Csaba
220108ebc6 Upgrade bree to 7.1 2021-12-03 22:39:38 +01:00
Balázs Csaba
984a3704e0 HU lang 2021-12-03 22:29:53 +01:00
Ioma Taani
909412c87e Merge branch 'louislam:master' into master 2021-12-03 11:11:38 +01:00
Ashish Bansal
481fd3a05f Updated monitor service details in README.md (#990)
* Updated monitor service details in README.md

Added `Steam Gamer Server' to the monitor service list.

* Update README.md

* Update README.md

* Update README.md
2021-12-03 15:49:46 +08:00
Louis Lam
5434e2da4f Merge pull request #993 from Saibamen/patch-1
Fix typos in markdown files
2021-12-03 15:48:57 +08:00
Ioma Taani
b3d348dcea Translation Update (#994)
* Better translation

* better translation

* aggiornato
2021-12-03 15:48:11 +08:00
Csaba Balázs
0aca0455ab HU language typo and missing items (#996)
* HU language

* run eslint on hu.js

* Last HU typo

* package.json valid required node engine syntax

Package.json required node engine version can contain multiple rules separated with ||. With this mode package-lock.json will be valid and error codes does not diplay.

Co-authored-by: Louis Lam <louislam@users.noreply.github.com>
2021-12-03 15:47:35 +08:00
Louis Lam
8f3ef734bc disable e2e test, as it is getting unstable recently on GitHub action 2021-12-03 01:31:19 +08:00
Ioma Taani
120eb0d85f aggiornato 2021-12-02 12:51:10 +01:00
Ioma Taani
4aaed0837e Merge branch 'louislam:master' into master 2021-12-02 12:44:13 +01:00
Adam Stachowicz
60657132c0 Update PULL_REQUEST_TEMPLATE.md 2021-12-02 11:19:59 +01:00
Adam Stachowicz
76cbef85d5 Update README.md 2021-12-02 11:18:30 +01:00
Adam Stachowicz
e17ef02008 Update CONTRIBUTING.md 2021-12-02 11:15:14 +01:00
Adam Stachowicz
f33d55c92d Update README.md 2021-12-02 11:09:27 +01:00
Jonas Liebert
67849a9e84 add support for stackfield notifications 2021-12-02 08:53:45 +01:00
Louis Lam
ee79a34148 Merge pull request #989 from BCsabaEngine/master
HU language typo
2021-12-02 12:44:18 +08:00
Louis Lam
d2f0480889 Merge pull request #988 from tgcentral/readme-md-typo
Typo correction
2021-12-02 12:43:19 +08:00
Balázs Csaba
c36190bba6 Node engine version 2021-12-01 15:40:22 +01:00
Balázs Csaba
4b3fae53d4 HU language typo 2021-12-01 15:32:39 +01:00
tgcentral
4dd60cba3d Typo correction 2021-12-01 09:43:58 +00:00
Ioma Taani
4bc84d2122 Merge branch 'louislam:master' into master 2021-12-01 08:47:57 +01:00
Louis Lam
a796f80018 Merge pull request #902 from ivanbratovic/improve-translatables
Fix untranslatable parts of the UI
2021-12-01 13:59:00 +08:00
Louis Lam
40cb22e671 Merge pull request #963 from kffl/feat/serwersms-provider
Add SerwerSMS.pl notification provider
2021-11-29 20:43:38 +08:00
Ivan Bratović
d95258e7db Merge remote-tracking branch 'upstream/master' into improve-translatables 2021-11-29 12:51:08 +01:00
Ivan Bratović
baae4b5a5e Remove unused translation keys from hr-HR 2021-11-29 12:49:38 +01:00
Ivan Bratović
c1b118a0f6 Use existing Example translation for HTTP headers and body placeholders 2021-11-29 12:49:08 +01:00
Ivan Bratović
9c5466890e Revert "Replace body and header placeholder functions with translations"
This reverts commit 2c85491ee0.
2021-11-29 12:40:53 +01:00
Louis Lam
bf8dbd78b3 temporary disable test for settings page 2021-11-29 17:25:30 +08:00
Louis Lam
6cd130de38 minor 2021-11-29 17:20:12 +08:00
Louis Lam
a864b72e03 fix pushover for general message 2021-11-29 17:19:55 +08:00
Louis Lam
5070927478 Merge pull request #980 from MrEddX/bulgarian
Update bg-BG.js
2021-11-29 16:56:02 +08:00
Louis Lam
bedc1f8617 Merge pull request #966 from louislam/lazy-load-lang
Lazy load language files
2021-11-29 16:54:25 +08:00
Louis Lam
077f3837d9 update language guide 2021-11-29 16:53:00 +08:00
Louis Lam
aea128a85b make settings' menu reactive 2021-11-29 16:50:00 +08:00
Louis Lam
c50b2b636a [lazy load lang] load the language file on create 2021-11-29 16:45:52 +08:00
MrEddX
a284703d9e Update bg-BG.js
- Fixed existing field
- Added new field
- Translated new field
2021-11-28 07:11:20 +02:00
kffl
64ec766423 translate(serwersms): fix pl translation capitalization
Co-authored-by: Adam Stachowicz <saibamenppl@gmail.com>
2021-11-27 13:22:54 +01:00
kffl
186c11540f style(serwersms): add missing trailing commas
Co-authored-by: Adam Stachowicz <saibamenppl@gmail.com>
2021-11-27 13:16:17 +01:00
Ioma Taani
4d947d9374 better translation 2021-11-26 10:37:42 +01:00
Ioma Taani
4888c97d86 Better translation 2021-11-26 10:33:15 +01:00
Louis Lam
50593f3edf [wip] lazy load language file 2021-11-26 16:31:19 +08:00
Paweł Kuffel
c1267e9b3b feat: add SerwerSMS notification provider 2021-11-25 18:24:36 +01:00
Ivan Bratović
2ca7a5b962 Merge branch 'master' into improve-translatables 2021-11-24 10:03:30 +01:00
Louis Lam
9f0c66d775 fix node-sqlite3 security issues 2021-11-24 14:53:15 +08:00
Louis Lam
a1f9a82537 Merge pull request #884 from thomasleveil/ux/add-group-at-the-top
🚸 Status page - add group action adds the new group at the top
2021-11-24 11:48:58 +08:00
Louis Lam
37e6ca8d77 Merge pull request #950 from dingdayu/master
Update dingding notification title
2021-11-24 11:36:00 +08:00
Louis Lam
0b0fd6609d Merge pull request #951 from iomataani/master
Updated translations.
2021-11-24 00:37:12 +08:00
Ioma Taani
3a32fd6f42 aggiornata la traduzione 2021-11-23 16:39:38 +01:00
Ioma Taani
97cb060cf5 another typo 2021-11-23 16:39:04 +01:00
Ioma Taani
5afb29f8f9 typo 2021-11-23 16:35:18 +01:00
Ioma Taani
f9b8dbf4db Merge branch 'louislam:master' into master 2021-11-23 16:30:48 +01:00
Louis Lam
92a5f18bf5 Merge pull request #864 from ivanbratovic/http-basicauth
Implement explicit HTTP "basic" authentication support
2021-11-23 22:48:54 +08:00
小雨
dce908a07b Update dingding notification title
Add the status to the title, you can see the message title on the friend list page.
2021-11-23 20:36:22 +08:00
Louis
4155f84eec improve basic auth style 2021-11-23 19:20:55 +08:00
Louis Lam
94ffeeeab6 update dependencies 2021-11-23 13:32:24 +08:00
Louis Lam
3d222ac5f5 fix btoa is not define 2021-11-23 12:59:48 +08:00
Louis Lam
c811c1ccde Merge pull request #753 from chakflying/settings-redesign
UI: Redesign/organize settings page
2021-11-23 12:46:59 +08:00
Ioma Taani
bd3d34400d Merge branch 'master' of https://github.com/iomataani/uptime-kuma 2021-11-22 23:39:10 +01:00
Louis Lam
5d3bf68123 add remove-2fa command 2021-11-18 18:22:03 +08:00
Nelson Chan
1f77526210 Chore: Run spell check 2021-11-17 14:09:12 +08:00
Nelson Chan
88ed965d69 Test: Disable clear stats test 2021-11-17 10:52:14 +08:00
Nelson Chan
7f4d5a0f76 Test: fix tests
Test: Attempt to fix tests

Test: Attempt to fix tests

Test: Attempt to fix tests

Test: Attempt to fix tests

Test: Attempt to fix tests

Test: Attempt to fix tests

Test: Attempt to fix tests

Test: Attempt to fix tests

Test: Attempt to fix tests

Test: Attempt to fix tests

Test: Attempt to fix tests

Test: Attempt to fix tests

Test: Attempt to fix tests

Test: Attempt to fix tests

Test: Investigate error message

Test: Attempt to fix tests

Chore: Cleanup code

Test: Attempt to fix tests

Test: Attempt to fix tests
2021-11-17 10:45:24 +08:00
Nelson Chan
df813fbdee Fix: Add save in monitor history 2021-11-17 10:45:19 +08:00
Nelson Chan
07742799ed Test: Fix tests
Test: Add clear stats test

Test: Attempt to fix tests

Test: Add test for disable auth

Update README
2021-11-17 10:45:19 +08:00
Nelson Chan
f65cc655c0 Fix: Fix nav highlight in settings sub-page 2021-11-17 10:45:19 +08:00
Nelson Chan
1a218aaa17 Fix: Fix page transition & improve active handling
Fix: Fix current route parsing
2021-11-17 10:45:19 +08:00
Nelson Chan
369cad90c1 WIP: Convert to use vue-router & improve layout
WIP: Fix security page & improve layout

WIP: Fix displaying current page

UI: Improve spacing

Chore: Improve styling
2021-11-17 10:45:18 +08:00
Nelson Chan
f9bb48de13 WIP: Convert Settings to components 2021-11-17 10:45:18 +08:00
Louis Lam
74d2b38cb6 Merge pull request #925 from iomataani/italian-language-update
Translation update
2021-11-16 19:12:07 +08:00
Louis Lam
7bba4fe2d0 Merge pull request #926 from MG8853/patch-1
Update ja.js
2021-11-16 18:53:30 +08:00
MultiGamer8853
be3a791e6e Update ja.js
日本語を最適化
2021-11-16 18:14:23 +09:00
Ioma Taani
9747048890 correzioni e miglioramenti 2021-11-16 09:04:10 +01:00
Phuong Nguyen Minh
d5d957b748 update vi.js (#853)
* update vi.js

* Update vi.js

* Update vi.js
2021-11-15 13:27:31 +08:00
Ioma Taani
5cdb5edeb3 corretto 2021-11-13 17:00:23 +01:00
Ioma Taani
73c18b6ff0 correzione 2021-11-13 16:53:07 +01:00
Ivan
567ea346fe Add missing translations for placeholders in EditMonitor page 2021-11-12 15:30:31 +01:00
Ivan
453f6fbadf Add more missing translations 2021-11-12 15:19:33 +01:00
Ivan
dd79042128 Add translation for "Info" in Settings 2021-11-12 15:14:28 +01:00
Ivan
583e6bf978 Update croatian language for testing new translation 2021-11-12 13:32:24 +01:00
Ivan
b1fca7c1a7 Add translation of toast success message 2021-11-12 12:00:10 +01:00
Ivan
19dd11d624 Add translation for incident error message 2021-11-12 10:14:23 +01:00
Ivan
42ce34b6c7 Add more Status page tranlations 2021-11-12 09:54:31 +01:00
Ivan
b7a9d1474f Fix translation of selected incident style 2021-11-12 09:53:41 +01:00
Ivan
31fa67452e Delint English language file 2021-11-12 09:31:27 +01:00
Ivan
9ef3727c91 Backed out of commit be1933614
Running update-language-files will just confuse translators
2021-11-12 09:29:06 +01:00
Ivan Bratović
ed39485af9 Merge with upstream master
Signed-off-by: Ivan Bratović <ivanbratovic4@gmail.com>
2021-11-12 09:23:20 +01:00
Ioma Taani
daef238a70 Updated Italian Language (#911)
Co-authored-by: Paride Barison <paride.barison@lantechlongwave.it>
2021-11-12 01:28:22 +08:00
Ivan
4cc433166e Add missing translation for SMTP security option 2021-11-11 11:14:21 +01:00
Ivan
28f530394e Add missing translation lookup for ClickSendSMS 2021-11-11 11:12:48 +01:00
Ivan
b0615d347b Add incident creation translations 2021-11-11 11:07:48 +01:00
Ivan
be19336149 Run update-language-files for all languages 2021-11-11 10:57:33 +01:00
Ivan
94508cae2f Add some missing translations 2021-11-11 10:54:36 +01:00
Ivan
265cca9ed1 Replace "Default" notification badge with translation 2021-11-11 10:54:09 +01:00
Ivan
267654c987 Replace hard-coded names for groups in Status page with translations 2021-11-11 10:53:38 +01:00
Ivan
2c85491ee0 Replace body and header placeholder functions with translations 2021-11-11 10:52:22 +01:00
Ivan Bratović
5d836cf05d [empty commit] pull request for updating translatables 2021-11-10 10:09:21 +01:00
Adam Stachowicz
ba46fb6b1c Clickable URL 2021-11-10 09:11:19 +01:00
Louis Lam
5df34cd137 Merge pull request #885 from ZegertBoele/patch-1
Update to dutch translations
2021-11-09 23:20:21 +08:00
Louis Lam
bf64095cea update to 1.10.2 2021-11-09 22:42:13 +08:00
Louis Lam
2333d1c7a7 Merge remote-tracking branch 'origin/master' 2021-11-09 22:37:20 +08:00
Louis Lam
95bae8289d Fix setting page when disabled auth 2021-11-09 22:37:05 +08:00
Louis Lam
45f7c647a6 Update feature_request.yaml 2021-11-09 21:31:04 +08:00
Zegert Boele
dff1056bb1 Update nl-NL.js
@deefdragon noticed a flaw, fixed in here
2021-11-09 09:52:09 +01:00
Zegert Boele
62222c0336 Update src/languages/nl-NL.js
@koen20 added this correction.

Co-authored-by: Koen Habets <6172623+koen20@users.noreply.github.com>
2021-11-09 09:49:42 +01:00
Louis Lam
733d0af75f update to 1.10.1 2021-11-08 18:03:00 +08:00
Louis Lam
b88e74fad8 Merge pull request #891 from ivanbratovic/croatian-language
Update Croatian languange
2021-11-08 17:35:13 +08:00
Ivan Bratović
734762b773 Merge with master
Signed-off-by: Ivan Bratović <ivanbratovic4@gmail.com>
2021-11-08 09:53:54 +01:00
Louis Lam
0275d7a42b minor 2021-11-08 15:51:32 +08:00
Louis Lam
41a6d1b701 Fix parseCertificateInfo possibly in dead loop 2021-11-08 15:39:17 +08:00
Ivan Bratović
34d8984e3a Merge branch 'master' into http-basicauth 2021-11-07 17:15:36 +01:00
Louis Lam
c92153c97e add more debug msg 2021-11-07 21:00:47 +08:00
Louis Lam
ad82ab0305 Merge pull request #883 from andreasbrett/patch-7
24h tooltip on status page
2021-11-07 17:24:03 +08:00
Louis Lam
f952d283c6 Merge pull request #815 from Fallstop/tags-on-status
Display Monitor Tags on Status Page
2021-11-07 17:05:21 +08:00
Louis Lam
e164fabf81 Merge pull request #849 from Minvinea/master
Adding missing translations
2021-11-07 13:56:15 +08:00
Louis Lam
bc69a331ee eslint 2021-11-07 13:50:22 +08:00
Jasper Miller-Waugh
e4506963d9 Merge branch 'louislam:master' into tags-on-status 2021-11-07 14:39:43 +13:00
Zegert Boele
222540898b Some translations were not translated
Some translations were not translated yet. I have added them and can be contacted if you want more English-Dutch translations.
2021-11-05 13:57:55 +01:00
Thomas LEVEIL
baf3612ece 🚸 Status page - add group action adds the new group at the top of the page 2021-11-05 11:27:19 +01:00
Andreas Brett
8f44b9f618 24h tooltip on status page 2021-11-05 09:54:10 +01:00
Louis Lam
210566c7af remove prefix for issue title, so users need to input the title 2021-11-05 11:34:50 +08:00
Ivan Bratović
0481a241f3 Add translated placeholders for editing basic auth 2021-11-04 10:22:42 +01:00
Ivan Bratović
179ca232bc Minor refactor - change variable names and add commas to object definitions 2021-11-04 10:14:17 +01:00
Ivan Bratović
0dcb7aed21 Delinting 2021-11-04 09:50:10 +01:00
Ivan Bratović
23736549f9 Implement HTTP basic auth feature 2021-11-04 09:50:10 +01:00
Ivan Bratović
665c263c03 Add db migrations for new basic auth fields 2021-11-04 09:50:10 +01:00
Louis Lam
c5e6628803 Merge pull request #870 from chakflying/patch-3
Update PULL_REQUEST_TEMPLATE.md
2021-11-04 11:55:06 +08:00
Louis Lam
3a1d8ddc11 Merge pull request #869 from MrEddX/bulgarian
Update bg-BG.js
2021-11-04 11:54:25 +08:00
Nelson Chan
bc5f61b3ec Chore: Add drag and drop
Co-authored-by: Adam Stachowicz <saibamenppl@gmail.com>
2021-11-04 10:21:57 +08:00
Adam Stachowicz
314fa18bdc Fix #871 2021-11-04 00:19:04 +01:00
Nelson Chan
57389fab2c Update PULL_REQUEST_TEMPLATE.md 2021-11-03 19:13:11 +08:00
MrEddX
ee2c54cfd1 Update bg-BG.js
- Added New Fields
- Fixed Existing Fields
2021-11-03 11:49:22 +02:00
Louis Lam
82cde7c847 Merge pull request #854 from 634750802/patch-1
Add a status prefix for feishu notification's title
2021-11-03 16:36:21 +08:00
Louis Lam
1ba2034701 freeze bootstrap to 5.1.3 to prevent breaking changes 2021-11-03 13:03:36 +08:00
Louis Lam
dee131c25d fix table hover color not working after updated bootstrap to 5.1.3 2021-11-03 13:02:44 +08:00
Jasper Miller-Waugh
e5d6410caf Apply formatting suggestions from code review
Co-authored-by: Adam Stachowicz <saibamenppl@gmail.com>
2021-11-03 11:46:53 +13:00
Louis Lam
e496c3b3be update healthcheck.js 2021-11-02 22:03:02 +08:00
Louis Lam
69f5112b38 Merge remote-tracking branch 'origin/master' 2021-11-02 21:49:02 +08:00
Louis Lam
c094dc0c5b speed up redirect by using 302 redirect instead of vue redirect 2021-11-02 21:48:46 +08:00
Ivan Bratović
1fb9b25d13 Improve hr-HR translations 2021-11-02 14:09:01 +01:00
Ivan Bratović
9a135deac2 Add new translation for 'Uptime' 2021-11-02 11:48:55 +01:00
Your Name
8e6173c05e Fix and add new hr-HR translations 2021-11-02 10:33:13 +01:00
Louis Lam
dec84282ed Update bug_report.yaml 2021-11-02 13:31:27 +08:00
Louis Lam
df80f413b5 Update feature_request.yaml 2021-11-02 13:31:19 +08:00
Louis Lam
17e59f1d8d Update ask-for-help.yaml 2021-11-02 13:30:39 +08:00
Louis Lam
973c2bb429 Update ask-for-help.yaml 2021-11-02 13:30:13 +08:00
Louis Lam
da0eaddeb8 Update SECURITY.md 2021-11-02 13:25:34 +08:00
Louis Lam
b2bc8d9db9 Update bug_report.yaml 2021-11-02 13:24:15 +08:00
Louis Lam
541068ff3b Update bug_report.yaml 2021-11-02 13:23:47 +08:00
Louis Lam
83ee46454a Update bug_report.yaml 2021-11-02 13:12:46 +08:00
Louis Lam
6d1baa329a draft for restructure status page 2021-11-02 12:26:22 +08:00
Louis Lam
75b21c905f Merge pull request #847 from Saibamen/update_pl
Update `pl.js`
2021-11-02 10:44:19 +08:00
Louis Lam
60e12f4bfa fix healthcheck.js with prefix UPTIME_KUMA_ 2021-11-02 01:13:05 +08:00
Jasper Miller-Waugh
191b81ee07 Fix grammer in comment
Co-authored-by: Nelson Chan <chakflying@hotmail.com>
2021-11-01 22:14:41 +13:00
Jagger
f3651a1219 Add a status prefix for feishu notification 2021-11-01 13:31:31 +08:00
Jasper Miller-Waugh
12ef9f39c5 Merged buttons, cleaned up SS tag retrieval and made tagsVisible a bool.
Also to note: due to the transition of tagsVisible this breaks compatibility with the previous commits, delete the  tagsVisible setting in the database to fix.
2021-11-01 13:23:46 +13:00
Jasper Miller-Waugh
4004926e64 Small formatting changes from code-review
Co-authored-by: deef <deef551@gmail.com>
2021-11-01 12:52:21 +13:00
Adam Stachowicz
4d3d6d6e25 Missing this... 2021-10-31 21:59:56 +01:00
Adam Stachowicz
d06e5ef6fa More small letters 2021-10-31 21:53:11 +01:00
Adam Stachowicz
b12b848d97 One more typo... 2021-10-31 21:48:43 +01:00
Adam Stachowicz
bb96a577ca Fix typos + translate wayToGetTeamsURL 2021-10-31 21:47:07 +01:00
Adam Stachowicz
8840ca618b Update pl.js 2021-10-31 21:22:19 +01:00
Minvinea
8ec858fd14 Add missing translation
Line 282 to 306
2021-10-31 21:18:36 +01:00
Louis Lam
124c98ce76 update to 1.10.0 2021-10-31 13:48:05 +08:00
Louis Lam
61135e8500 update package-lock.json 2021-10-31 13:18:06 +08:00
Louis Lam
08a58dec2b fix manifest.json path 2021-10-31 13:17:34 +08:00
Louis Lam
741ed548da Merge branch 'master' into use_hideCount_option 2021-10-31 13:09:50 +08:00
Louis Lam
52d80d3a5d Merge remote-tracking branch 'origin/master' 2021-10-31 13:08:00 +08:00
Louis Lam
586c748d44 vite build on host machine, since it is very slow on armv7/arm64 build 2021-10-31 13:07:10 +08:00
Louis Lam
b5d6e96b1d Merge pull request #842 from gaby/issue-form-updates
Update the wording used when reporting an Issue via Forms
2021-10-31 10:49:24 +08:00
Juan Calderon-Perez
68b74f07e4 Update wording of form field 2021-10-30 19:06:32 -04:00
Louis Lam
bc615c2dd8 Merge pull request #832 from ivanbratovic/croatian-language
Clarity and grammar fixes to hr-HR languange file
2021-10-31 00:52:41 +08:00
Louis Lam
e7104737e7 prevent to show "New Update" when the Docker image is not ready yet 2021-10-31 00:36:46 +08:00
Louis Lam
1dbf1c3dea fix logout 2021-10-30 23:33:20 +08:00
Jasper Miller-Waugh
74688e69aa Remove debug statement in server/routers/api-router.js
Co-authored-by: Adam Stachowicz <saibamenppl@gmail.com>
2021-10-30 21:16:10 +13:00
Jasper Miller-Waugh
b32bfb3ff1 Added toggle for tag visibility 2021-10-30 21:16:10 +13:00
Jasper Miller-Waugh
24664cde2c Smarter CSS to fix Mobile alignment 2021-10-30 21:16:10 +13:00
Jasper Miller-Waugh
348c5ec995 Match lint settings 2021-10-30 21:16:10 +13:00
Jasper Miller-Waugh
9143b73f84 Styling for tags 2021-10-30 21:16:10 +13:00
Jasper Miller-Waugh
5e6d945095 Most hacked in POC 2021-10-30 21:16:10 +13:00
Louis
ba93129b18 Merge branch '1.9.X'
# Conflicts:
#	server/server.js
2021-10-30 10:05:08 +08:00
Willian Rodrigues Barbosa
036218f711 Add badges to favicon 2021-10-29 22:25:32 -03:00
Adam Stachowicz
69aa60d1fb Add missing i18n key + translate 2 keys for polish 2021-10-29 17:01:29 +02:00
Ivan Bratović
eaecd6e571 Clarity and grammar fixes to hr_HR languange file 2021-10-29 16:53:55 +02:00
Adam Stachowicz
f2a27a2cf1 Use hideCount option for pagination 2021-10-29 13:37:38 +02:00
Louis
d4c9431142 catch createWriteStream error 2021-10-29 18:28:31 +08:00
Louis
d7f7dba13f write some error logs to ./data/error.log 2021-10-29 18:24:47 +08:00
Louis
3e5ae00d25 update dependencies 2021-10-29 18:04:58 +08:00
Louis Lam
5311bef3eb Merge pull request #809 from Saibamen/pagination_no_text
Don't display "count" text for pagination
2021-10-29 16:05:59 +08:00
Louis Lam
c400595f67 Merge pull request #825 from SiderealArt/patch-1
Add Traditional Chinese (Taiwan) translation
2021-10-29 16:05:01 +08:00
Louis Lam
e84f7dac60 Merge pull request #821 from ivanbratovic/croatian-language
Add Croatian language
2021-10-29 16:03:28 +08:00
SiderealArt
67a22399bc Update i18n.js 2021-10-29 15:44:33 +08:00
SiderealArt
947fc6001e Update Settings.vue 2021-10-29 15:43:21 +08:00
SiderealArt
c87e67ad1b Add Traditional Chinese (Taiwan) translation 2021-10-29 15:39:02 +08:00
Ivan Bratović
6f92774a8f ESLint pass on hr-HR.js file 2021-10-28 16:17:07 +02:00
Ivan Bratović
6e76ab7426 Import hr-HR language in i18n.js 2021-10-28 16:07:47 +02:00
Ivan Bratović
b2fbd7e263 Add confirmDisableAuth tranlation for hr-HR 2021-10-28 16:04:56 +02:00
Ivan Bratović
199e6ec82b Add initial version of Croatian language 2021-10-28 15:56:34 +02:00
Adam Stachowicz
18a99c2016 Revert deleting records and "One record" keys 2021-10-28 14:20:56 +02:00
Louis Lam
e261a27ebe fix wrong call of rejectUnauthorized for smtp (#757) 2021-10-28 11:10:09 +08:00
Louis Lam
de5cce9d90 Steam API Key to HiddenInput 2021-10-27 23:03:03 +08:00
Louis Lam
b85c9186f9 Merge pull request #781 from louislam/cert-notification
Certificate renewal notifications
2021-10-27 22:43:43 +08:00
Louis Lam
eb22ad5ffe [certificate notification] error handling and better msg 2021-10-27 16:12:18 +08:00
Louis Lam
f5f4835b74 [certificate notification] clear sent history if the cert is changed 2021-10-27 16:03:16 +08:00
Louis Lam
44c1b336dc send certificate notifications in 21, 14, 7 days 2021-10-27 15:33:15 +08:00
Louis Lam
110ec491ee Merge branch 'master' into cert-notification 2021-10-27 14:16:37 +08:00
Louis Lam
640b6e5b1c prevent monitor dead for unexpected error 2021-10-27 14:08:44 +08:00
Louis Lam
234fba3978 Update SECURITY.md 2021-10-27 12:31:18 +08:00
Louis Lam
1285ccb537 Merge pull request #784 from gaby/issue-forms
Migrate issue templates to Github Issue Forms
2021-10-27 12:29:44 +08:00
Juan Calderon-Perez
6c542edfc9 Add examples for version and image tag. 2021-10-27 00:23:13 -04:00
Juan Calderon-Perez
767807dd22 Added text placeholders for versions. 2021-10-27 00:18:05 -04:00
Juan Calderon-Perez
546402f3d2 Added docker-image-tag field. 2021-10-27 00:11:50 -04:00
Louis Lam
698a38e773 clear status page cache in a better place 2021-10-27 11:39:46 +08:00
Louis Lam
71884cf42a Merge pull request #772 from andreasbrett/patch-6
show logged in user on settings page
2021-10-27 10:24:45 +08:00
Louis Lam
d676c782bb Update README.md 2021-10-27 10:11:08 +08:00
Juan Calderon-Perez
dd773aa5a2 Updates based on PR comments. 2021-10-26 22:10:48 -04:00
Louis Lam
2852e59ffb Merge pull request #812 from MrEddX/bulgarian
Update bg-BG.js
2021-10-27 09:37:42 +08:00
MrEddX
cb3da50e7e Update bg-BG.js
Small fixes
2021-10-26 22:46:57 +03:00
Louis Lam
f25653d778 Update README.md 2021-10-27 00:07:17 +08:00
Adam Stachowicz
2e7ad1b7b2 Don't display "count" text for pagination 2021-10-26 18:03:49 +02:00
Louis Lam
e0e1ab6fa6 Update CONTRIBUTING.md 2021-10-27 00:02:20 +08:00
Louis Lam
8d984881c9 Update CONTRIBUTING.md 2021-10-26 23:56:14 +08:00
Louis Lam
955f9ae20a Merge pull request #794 from louislam/free-disk-space
Free Disk Space
2021-10-26 23:09:27 +08:00
Louis Lam
a9e319517a add auto vacuum and shrink database button 2021-10-26 23:02:32 +08:00
Louis Lam
39ad8b4bb7 Merge branch 'master' into free-disk-space 2021-10-26 21:18:22 +08:00
Andreas Brett
8fb8cbdaf3 use auth0/jwt-decode 2021-10-26 13:15:31 +02:00
Andreas Brett
3f3d8b4eb3 fixes 2021-10-26 12:58:04 +02:00
Louis Lam
9123e9461f Update CONTRIBUTING.md 2021-10-26 16:38:00 +08:00
Louis Lam
77addfebc8 Merge branch 'tarun7singh_master'
# Conflicts:
#	src/languages/vi.js
2021-10-26 16:26:59 +08:00
Louis Lam
16846c7c6d Merge pull request #790 from Saibamen/pl_update+fixes
PL update + fixes
2021-10-26 16:25:04 +08:00
Louis Lam
d1c4d13903 Merge pull request #782 from Co2333/dev-lakr233-bark_notification
Support for Bark (APN) notifications
2021-10-26 16:23:11 +08:00
Louis Lam
7cd4bfc11d Merge pull request #806 from Saibamen/patch-1
Update PULL_REQUEST_TEMPLATE.md
2021-10-26 15:58:14 +08:00
Adam Stachowicz
1d5c0502ab Add other 2021-10-26 09:48:17 +02:00
Adam Stachowicz
a1cda93ad5 Update PULL_REQUEST_TEMPLATE.md
See https://github.com/louislam/uptime-kuma/pull/785#issuecomment-950521657
2021-10-26 09:46:12 +02:00
Louis Lam
3bd420f0e0 Merge pull request #752 from chakflying/feat/chart-period
Feat: Add options for Chart period
2021-10-26 12:53:42 +08:00
Louis Lam
78424b4f2d add simple loading chart effect 2021-10-26 12:48:21 +08:00
Louis Lam
f8055ed03d minor css 2021-10-26 12:33:46 +08:00
Louis Lam
fe4724fc53 Merge branch 'master' into feat/chart-period 2021-10-26 12:25:07 +08:00
Louis Lam
7f0dda6a44 Update CONTRIBUTING.md 2021-10-26 11:28:38 +08:00
Lakr Aream
43791ee97e Merge branch 'master' into dev-lakr233-bark_notification 2021-10-26 09:08:11 +08:00
Tarun Singh
6362ef6a9c removed other langauge translation data 2021-10-25 18:30:40 -04:00
Adam Stachowicz
9d3a4e9d1e Remove dot from needPushEvery 2021-10-26 00:00:54 +02:00
Tarun Singh
6c60096f56 Merge branch 'louislam:master' into master 2021-10-25 04:12:39 -04:00
Tarun Singh
ba1e025353 added click send to number 2021-10-25 04:11:57 -04:00
Louis Lam
a41a081727 Merge remote-tracking branch 'origin/master' 2021-10-25 12:29:53 +08:00
Louis Lam
a5f15f2319 Fix #793, check dist-backup existing before delete 2021-10-25 12:29:40 +08:00
Louis Lam
e69799f613 Update CONTRIBUTING.md 2021-10-25 12:07:44 +08:00
Louis Lam
3c795bebe3 Update CONTRIBUTING.md 2021-10-25 12:06:49 +08:00
Louis Lam
3a43fec666 add recommend pull request guideline 2021-10-25 12:06:01 +08:00
Louis Lam
24c645e437 [empty commit] pull request for free-disk-space 2021-10-25 10:59:13 +08:00
Louis Lam
9d31da1fe8 Merge pull request #792 from gaby/stalebot-fix
Rename stale-bot to stale-bot.yml
2021-10-25 10:26:15 +08:00
Juan Calderon-Perez
2e4c42941a Rename stale-bot to stale-bot.yml 2021-10-24 21:31:10 -04:00
Andreas Brett
4fc2603818 Merge branch 'patch-6' of https://github.com/andreasbrett/uptime-kuma into patch-6 2021-10-25 01:34:06 +02:00
Andreas Brett
7bc38d4231 remove unused vars 2021-10-25 01:33:45 +02:00
Andreas Brett
daad63d70b Merge branch 'master' into patch-6 2021-10-25 01:24:31 +02:00
Andreas Brett
9ddd2c7365 use jwt 2021-10-25 01:23:02 +02:00
Adam Stachowicz
fa5ba12e14 Missing this 2021-10-24 22:15:02 +02:00
Adam Stachowicz
85053f865e Fix typo 2021-10-24 21:56:37 +02:00
Adam Stachowicz
1239f6d1a2 PL update + fixes 2021-10-24 21:50:02 +02:00
Tarun Singh
fed611d1b9 Merge branch 'master' of https://github.com/louislam/uptime-kuma 2021-10-24 15:19:25 -04:00
Tarun Singh
bc68088350 Click send sms integration for notifications 2021-10-24 15:17:29 -04:00
Louis Lam
90200958cd Merge pull request #780 from kry008/patch-4
Update pl.js
2021-10-25 00:35:31 +08:00
Louis Lam
aa13d74d7a Merge pull request #788 from Ponkhy/german-language
Updated german language file
2021-10-25 00:32:40 +08:00
Ponkhy
d82f305f6e Updated german language file 2021-10-24 17:56:05 +02:00
Louis Lam
7c63cbfd84 add node.js 17 to auto test 2021-10-24 22:50:33 +08:00
Juan Calderon-Perez
c7e1267779 More syntax changes. Change What to Which 2021-10-24 10:12:17 -04:00
Louis Lam
5d0b54c292 Merge pull request #785 from Saibamen/patch-1
Create PULL_REQUEST_TEMPLATE.md
2021-10-24 22:07:46 +08:00
Juan Calderon-Perez
b50b390048 Changes based on PR requests 2021-10-24 10:04:56 -04:00
Adam Stachowicz
65158cb06b Update PULL_REQUEST_TEMPLATE.md
Changes after Code Review
2021-10-24 12:21:59 +02:00
Adam Stachowicz
8fe5e4e605 Fix lint 2021-10-24 11:19:42 +02:00
Adam Stachowicz
ab5ddae2ee Delete double new line 2021-10-24 11:18:07 +02:00
Adam Stachowicz
89c64f4ea2 Create PULL_REQUEST_TEMPLATE.md
From https://github.com/Harsha200105/DesktopAssistant/pull/51
2021-10-24 11:16:29 +02:00
Juan Calderon-Perez
40a1ebecc5 Update feature_request.yaml 2021-10-24 00:51:47 -04:00
Juan Calderon-Perez
e1793596fe Merge pull request #1 from gaby/issue-forms 2021-10-24 00:49:18 -04:00
Juan Calderon-Perez
c489058a57 Update labels for each form 2021-10-24 00:46:10 -04:00
Juan Calderon-Perez
95342ec006 Update label for ask for help 2021-10-24 00:41:51 -04:00
Juan Calderon-Perez
bdebbf8e40 Add Support for Issue Forms 2021-10-24 00:40:06 -04:00
Juan Calderon-Perez
9a9fca67d5 Migrate Bug Report to Github Issue Forms 2021-10-23 22:58:35 -04:00
Nelson Chan
665bae0806 UI: Simplify dropdown design 2021-10-24 01:45:57 +08:00
Nelson Chan
e4be28a9e7 Fix: Validate beat time before appending 2021-10-24 01:45:38 +08:00
Nelson Chan
445674aacb Chore: Improve code formatting & comments 2021-10-24 01:45:10 +08:00
Nelson Chan
2f7b60f5e5 Feat: Use separate storage for custom chart period
Fix: Fix import error
2021-10-24 01:44:55 +08:00
Nelson Chan
b83c59e308 WIP: Add options for chart period
Fix: Fix callback, add toast on error

Fix: Improve styling

Fix: Restore default chart behavior

Fix: Replace 1h with 3h

draft only
2021-10-24 01:44:08 +08:00
Lakr Aream
ce852dfa02 Support for Bark (APN) notifications
Update bark.js
2021-10-24 00:31:43 +08:00
Louis Lam
c9549c0de2 change body and header placeholders, less misleading. 2021-10-23 22:14:05 +08:00
Louis Lam
957c292307 add certificate-notification job 2021-10-23 21:44:21 +08:00
kry008
b5eb17ed93 Update pl.js 2021-10-23 15:07:24 +02:00
Louis Lam
d578300104 Merge pull request #743 from andreasbrett/patch-4
Harden 2FA/TOTP implementation according to rfc6238 (part 2)
2021-10-23 17:00:46 +08:00
Louis Lam
b77b33e790 add login rate limiter 2021-10-23 16:35:13 +08:00
Andreas Brett
4becb97a5d Update en.js 2021-10-23 01:54:36 +02:00
Andreas Brett
85e2b36424 Update en.js 2021-10-23 01:54:07 +02:00
Andreas Brett
abdf1ae90a Update en.js 2021-10-23 01:53:31 +02:00
Andreas Brett
606c967985 Merge branch 'master' into patch-6 2021-10-23 01:52:54 +02:00
Andreas Brett
93c231b4d9 Update server/server.js
Co-authored-by: Adam Stachowicz <saibamenppl@gmail.com>
2021-10-23 01:52:02 +02:00
Andreas Brett
9ad8e5f56a show logged in user 2021-10-22 13:05:52 +02:00
Louis Lam
8a481a1be0 Merge pull request #769 from andreasbrett/patch-5
show beat.msg only if available
2021-10-22 18:14:52 +08:00
Andreas Brett
657987a013 fix: show beat.msg only if available 2021-10-22 11:59:53 +02:00
Louis Lam
d74577608b Merge pull request #766 from DX37/translation-ru
Update notifications area in ru-RU.js
2021-10-22 14:10:15 +08:00
Louis Lam
20a399c557 Merge pull request #767 from mrphuongbn/master
Add Vietnamese language
2021-10-22 14:04:32 +08:00
Phuong Nguyen Minh
060dde9827 Merge branch 'louislam:master' into master 2021-10-22 08:08:22 +07:00
DX37
1d1601cf24 Update notifications area in ru-RU.js 2021-10-21 22:49:17 +07:00
Louis Lam
ff5f2e8dfb add limiter 2021-10-21 23:03:40 +08:00
Louis Lam
5451fb7672 Merge remote-tracking branch 'origin/master' 2021-10-21 22:54:21 +08:00
Louis Lam
56094a43d7 add passwordStrength 2021-10-21 22:54:04 +08:00
Louis Lam
68bbe8944a Merge pull request #756 from DX37/translation-ru
Update ru-RU.js
2021-10-21 10:56:54 +08:00
Louis Lam
8f1da6aa22 Update CONTRIBUTING.md 2021-10-21 00:08:46 +08:00
Louis Lam
c0d6fe0d76 Update CONTRIBUTING.md 2021-10-21 00:03:55 +08:00
DX37
29e4e41215 Update ru-RU.js 2021-10-20 23:00:29 +07:00
Louis Lam
7a1bb964e9 Update bug_report.md 2021-10-20 18:09:23 +08:00
Louis Lam
3fe0e9bf1e Update ask-for-help.md 2021-10-20 18:09:10 +08:00
Louis Lam
9982887783 Update feature_request.md 2021-10-20 18:08:52 +08:00
Louis Lam
c31efc0ef4 Merge pull request #748 from MrEddX/bulgarian
Fix: Integration Link Text
2021-10-20 14:37:45 +08:00
MrEddX
6463d4b209 Fix: Integration Link Text
Maybe left there from the Slack template.
2021-10-20 09:09:08 +03:00
Louis Lam
acada8028a Merge pull request #747 from MrEddX/bulgarian
Update bg-BG.js
2021-10-20 11:58:12 +08:00
MrEddX
d0b0c64b81 Update bg-BG.js
- Fixed existing fields
- Added new fields
- Translated new fields
2021-10-19 23:03:46 +03:00
Louis Lam
cd04ac4557 "dist/index.html" is no longer needed for development environment 2021-10-20 01:32:19 +08:00
Louis Lam
d7d2f7b7fc Merge pull request #745 from dpatrongomez/patch-2
Add steam translation in spanish
2021-10-20 00:46:55 +08:00
Daniel Patrón Gómez
5a05d135b8 Add steam translation in spanish 2021-10-19 17:11:32 +02:00
Louis Lam
e03ee593e2 Merge pull request #716 from NeuralMiner/textchanges
Text update
2021-10-19 16:34:00 +08:00
Louis Lam
6c1ee70e15 fix 2021-10-19 16:29:09 +08:00
Louis Lam
5c3892313e add env var: UPTIME_KUMA_DISABLE_FRAME_SAMEORIGIN 2021-10-19 14:41:05 +08:00
Louis Lam
c57c94642c Merge remote-tracking branch 'origin/master' 2021-10-19 14:26:26 +08:00
Louis Lam
62f168a2a5 config response header 2021-10-19 14:26:10 +08:00
Louis Lam
c808f78f09 Merge pull request #735 from gaby/stale-bot
Support for closing stale Issues/PRs after 6 months of inactivity
2021-10-19 11:59:37 +08:00
Louis Lam
9c80e1c732 Merge pull request #641 from andreasbrett/patch-1
Harden 2FA/TOTP implementation according to rfc6238 (part 1)
2021-10-19 10:17:04 +08:00
Andreas Brett
acc2995d86 invalidate used token 2021-10-19 00:42:33 +02:00
Andreas Brett
7def9dcec7 Merge branch 'louislam:master' into patch-1 2021-10-19 00:37:15 +02:00
NeuralMiner
a35569481d Updates 2021-10-18 15:26:38 -06:00
NeuralMiner
9ddffc0f7f Updates 2021-10-18 14:35:47 -06:00
NeuralMiner
76e7c8b276 Rebase 2021-10-18 14:35:36 -06:00
NeuralMiner
572a5300aa Recommended updates. 2021-10-18 13:25:53 -06:00
NeuralMiner
e1f1d4a959 Merge branch 'louislam:master' into textchanges 2021-10-18 12:57:28 -06:00
Juan Calderon-Perez
b5857f7c0c Fix syntax issue. 2021-10-18 08:23:18 -04:00
Juan Calderon-Perez
6277babf25 Migrate to actions/stale v4.0 2021-10-18 08:22:08 -04:00
Louis Lam
300a95d779 recompile util.js with tsconfig.json 2021-10-18 17:51:40 +08:00
Louis Lam
23714ab688 genSecret don't need await 2021-10-18 17:37:11 +08:00
Louis Lam
16b44001e7 Merge remote-tracking branch 'andreasbrett/patch-1' into andreasbrett_patch-1
# Conflicts:
#	src/util.js
2021-10-18 17:35:17 +08:00
Louis Lam
f2f8f33b86 Merge branch 'master' into andreasbrett_patch-1
# Conflicts:
#	src/util.js
2021-10-18 17:30:21 +08:00
Andreas Brett
df4682d19b Merge branch 'master' into patch-1 2021-10-18 01:14:58 +02:00
Andreas Brett
11a1f35cc5 independent csprng solution 2021-10-18 01:06:20 +02:00
Juan Calderon-Perez
2a3ce15328 Use default number of operations per day. Defaults to 30 2021-10-17 17:23:44 -04:00
Juan Calderon-Perez
7cb25255bf Update stale-bot 2021-10-17 17:20:32 -04:00
Juan Calderon-Perez
c622f7958f Add support for closing stale Issues/PR 2021-10-17 17:16:07 -04:00
NeuralMiner
8cb26d2b31 Text update 2021-10-15 19:12:09 -06:00
Phuong Nguyen Minh
bda481c61e Update vi.js 2021-10-15 08:26:33 +07:00
Andreas Brett
86dcc9bc8f import webcrypto lib 2021-10-13 17:34:56 +02:00
Louis Lam
145b722aec Merge branch 'master' into andreasbrett_patch-1 2021-10-13 22:17:33 +08:00
Louis Lam
79c81395bc Merge branch 'master' into andreasbrett_patch-1 2021-10-13 22:02:27 +08:00
Phuong Nguyen Minh
89c0f8b734 Update i18n.js 2021-10-13 13:44:31 +07:00
Phuong Nguyen Minh
dc805cff97 Add files via upload 2021-10-13 13:36:09 +07:00
Andreas Brett
dc1de50a02 fix for max-inclusive 2021-10-11 01:18:33 +02:00
Andreas Brett
0e6d7694ce Update util.js 2021-10-10 23:54:45 +02:00
Andreas Brett
11bcd1e2ed const 2021-10-10 22:55:32 +02:00
Andreas Brett
06310423f4 typo 2021-10-10 22:19:10 +02:00
Andreas Brett
e127e168b6 typed parameters 2021-10-10 22:15:42 +02:00
Andreas Brett
075535ba46 Update util.ts 2021-10-10 21:59:23 +02:00
Andreas Brett
13cf6891ac cryptographically strong secret generation
generate TOTP secret using WebCrypto API (see https://github.com/louislam/uptime-kuma/issues/640)
2021-10-10 21:58:23 +02:00
168 changed files with 23294 additions and 9132 deletions

View File

@@ -1,5 +1,4 @@
/.idea
/dist
/node_modules
/data
/out

View File

@@ -1,21 +0,0 @@
---
name: Ask for help
about: You can ask any question related to Uptime Kuma.
title: ''
labels: help
assignees: ''
---
**Is it a duplicate question?**
Please search in Issues without filters: https://github.com/louislam/uptime-kuma/issues?q=
**Describe your problem**
Please describe what you are asking for
**Info**
Uptime Kuma Version:
Using Docker?: Yes/No
Docker Version:
Node.js Version (Without Docker only):
OS:
Browser:

View File

@@ -0,0 +1,68 @@
name: "❓ Ask for help"
description: "Submit any question related to Uptime Kuma"
#title: "[Help] "
labels: [help]
body:
- type: checkboxes
id: no-duplicate-issues
attributes:
label: "⚠️ Please verify that this bug has NOT been raised before."
description: "Search in the issues sections by clicking [HERE](https://github.com/louislam/uptime-kuma/issues?q=)"
options:
- label: "I checked and didn't find similar issue"
required: true
- type: checkboxes
attributes:
label: "🛡️ Security Policy"
description: Please review the security policy before reporting security related issues/bugs.
options:
- label: I agree to have read this project [Security Policy](https://github.com/louislam/uptime-kuma/security/policy)
required: true
- type: textarea
id: steps-to-reproduce
validations:
required: true
attributes:
label: "📝 Describe your problem"
description: "Please walk us through it step by step."
placeholder: "Describe what are you asking for..."
- type: input
id: uptime-kuma-version
attributes:
label: "🐻 Uptime-Kuma Version"
description: "Which version of Uptime-Kuma are you running? Please do NOT provide the docker tag such as latest or 1"
placeholder: "Ex. 1.10.0"
validations:
required: true
- type: input
id: operating-system
attributes:
label: "💻 Operating System and Arch"
description: "Which OS is your server/device running on?"
placeholder: "Ex. Ubuntu 20.04 x86"
validations:
required: true
- type: input
id: browser-vendor
attributes:
label: "🌐 Browser"
description: "Which browser are you running on?"
placeholder: "Ex. Google Chrome 95.0.4638.69"
validations:
required: true
- type: input
id: docker-version
attributes:
label: "🐋 Docker Version"
description: "If running with Docker, which version are you running?"
placeholder: "Ex. Docker 20.10.9 / K8S / Podman"
validations:
required: false
- type: input
id: nodejs-version
attributes:
label: "🟩 NodeJS Version"
description: "If running with Node.js? which version are you running?"
placeholder: "Ex. 14.18.0"
validations:
required: false

View File

@@ -1,42 +0,0 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: bug
assignees: ''
---
**Is it a duplicate question?**
Please search in Issues without filters: https://github.com/louislam/uptime-kuma/issues?q=
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Info**
Uptime Kuma Version:
Using Docker?: Yes/No
Docker Version:
Node.js Version (Without Docker only):
OS:
Browser:
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Error Log**
It is easier for us to find out the problem.
Docker: `docker logs <container id>`
PM2: `~/.pm2/logs/` (e.g. `/home/ubuntu/.pm2/logs`)

99
.github/ISSUE_TEMPLATE/bug_report.yaml vendored Normal file
View File

@@ -0,0 +1,99 @@
name: "🐛 Bug Report"
description: "Submit a bug report to help us improve"
#title: "[Bug] "
labels: [bug]
body:
- type: checkboxes
id: no-duplicate-issues
attributes:
label: "⚠️ Please verify that this bug has NOT been raised before."
description: "Search in the issues sections by clicking [HERE](https://github.com/louislam/uptime-kuma/issues?q=)"
options:
- label: "I checked and didn't find similar issue"
required: true
- type: checkboxes
attributes:
label: "🛡️ Security Policy"
description: Please review the security policy before reporting security related issues/bugs.
options:
- label: I agree to have read this project [Security Policy](https://github.com/louislam/uptime-kuma/security/policy)
required: true
- type: textarea
id: description
validations:
required: false
attributes:
label: "Description"
description: "You could also upload screenshots"
- type: textarea
id: steps-to-reproduce
validations:
required: true
attributes:
label: "👟 Reproduction steps"
description: "How do you trigger this bug? Please walk us through it step by step."
placeholder: "..."
- type: textarea
id: expected-behavior
validations:
required: true
attributes:
label: "👀 Expected behavior"
description: "What did you think would happen?"
placeholder: "..."
- type: textarea
id: actual-behavior
validations:
required: true
attributes:
label: "😓 Actual Behavior"
description: "What actually happen?"
placeholder: "..."
- type: input
id: uptime-kuma-version
attributes:
label: "🐻 Uptime-Kuma Version"
description: "Which version of Uptime-Kuma are you running? Please do NOT provide the docker tag such as latest or 1"
placeholder: "Ex. 1.10.0"
validations:
required: true
- type: input
id: operating-system
attributes:
label: "💻 Operating System and Arch"
description: "Which OS is your server/device running on?"
placeholder: "Ex. Ubuntu 20.04 x86"
validations:
required: true
- type: input
id: browser-vendor
attributes:
label: "🌐 Browser"
description: "Which browser are you running on?"
placeholder: "Ex. Google Chrome 95.0.4638.69"
validations:
required: true
- type: input
id: docker-version
attributes:
label: "🐋 Docker Version"
description: "If running with Docker, which version are you running?"
placeholder: "Ex. Docker 20.10.9 / K8S / Podman"
validations:
required: false
- type: input
id: nodejs-version
attributes:
label: "🟩 NodeJS Version"
description: "If running with Node.js? which version are you running?"
placeholder: "Ex. 14.18.0"
validations:
required: false
- type: textarea
id: logs
attributes:
label: "📝 Relevant log output"
description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks.
render: shell
validations:
required: false

View File

@@ -1,22 +0,0 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: enhancement
assignees: ''
---
**Is it a duplicate question?**
Please search in Issues without filters: https://github.com/louislam/uptime-kuma/issues?q=
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

View File

@@ -0,0 +1,59 @@
name: 🚀 Feature Request
description: "Submit a proposal for a new feature"
#title: "[Feature] "
labels: [feature-request]
body:
- type: checkboxes
id: no-duplicate-issues
attributes:
label: "⚠️ Please verify that this feature request has NOT been suggested before."
description: "Search in the issues sections by clicking [HERE](https://github.com/louislam/uptime-kuma/issues?q=)"
options:
- label: "I checked and didn't find similar feature request"
required: true
- type: dropdown
id: feature-area
attributes:
label: "🏷️ Feature Request Type"
description: "What kind of feature request is this?"
multiple: true
options:
- API
- New Notification
- New Monitor
- UI Feature
- Other
validations:
required: true
- type: textarea
id: feature-description
validations:
required: true
attributes:
label: "🔖 Feature description"
description: "A clear and concise description of what the feature request is."
placeholder: "You should add ..."
- type: textarea
id: solution
validations:
required: true
attributes:
label: "✔️ Solution"
description: "A clear and concise description of what you want to happen."
placeholder: "In my use-case, ..."
- type: textarea
id: alternatives
validations:
required: false
attributes:
label: "❓ Alternatives"
description: "A clear and concise description of any alternative solutions or features you've considered."
placeholder: "I have considered ..."
- type: textarea
id: additional-context
validations:
required: false
attributes:
label: "📝 Additional Context"
description: "Add any other context or screenshots about the feature request here."
placeholder: "..."

28
.github/PULL_REQUEST_TEMPLATE.md vendored Normal file
View File

@@ -0,0 +1,28 @@
# Description
Fixes #(issue)
## Type of change
Please delete any options that are not relevant.
- Bug fix (non-breaking change which fixes an issue)
- User interface (UI)
- New feature (non-breaking change which adds functionality)
- Breaking change (fix or feature that would cause existing functionality to not work as expected)
- Translation update
- Other
- This change requires a documentation update
## Checklist
- [ ] My code follows the style guidelines of this project
- [ ] I ran ESLint and other linters for modified files
- [ ] I have performed a self-review of my own code and tested it
- [ ] I have commented my code, particularly in hard-to-understand areas
- [ ] My changes generate no new warnings
- [ ] My code needed automated testing. I have added them (this is optional task)
## Screenshots (if any)
Please do not use any external image service. Instead, just paste in or drag and drop the image here, and it will be uploaded automatically.

View File

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

View File

@@ -0,0 +1,26 @@
name: Close Incorrect Issue
on:
issues:
types: [opened]
jobs:
close-incorrect-issue:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest]
node-version: [16.x]
steps:
- uses: actions/checkout@v2
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v2
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
- run: npm ci
- run: node extra/close-incorrect-issue.js ${{ secrets.GITHUB_TOKEN }} ${{ github.event.issue.number }} ${{ github.event.issue.user.login }}

View File

@@ -1,8 +1,8 @@
# Project Info
First of all, thank you everyone who made pull requests for Uptime Kuma, I never thought GitHub Community can be that nice! And also because of this, I also never thought other people actually read my code and edit my code. It is not structed and commented so well, lol. Sorry about that.
First of all, thank you everyone who made pull requests for Uptime Kuma, I never thought GitHub Community can be that nice! And also because of this, I also never thought other people actually read my code and edit my code. It is not structured and commented so well, lol. Sorry about that.
The project was created with vite.js (vue3). Then I created a sub-directory called "server" for server part. Both frontend and backend share the same package.json.
The project was created with vite.js (vue3). Then I created a subdirectory called "server" for server part. Both frontend and backend share the same package.json.
The frontend code build into "dist" directory. The server (express.js) exposes the "dist" directory as root of the endpoint. This is how production is working.
@@ -27,33 +27,36 @@ The frontend code build into "dist" directory. The server (express.js) exposes t
## Can I create a pull request for Uptime Kuma?
Generally, if the pull request is working fine and it do not affect any existing logic, workflow and perfomance, I will merge into the master branch once it is tested.
⚠️ 2022-03-02 Update:
If you are not sure, feel free to create an empty pull request draft first.
Since I found that merging pull requests is a pretty heavy task for me, I try to rearrange it.
### Pull Request Examples
#### ✅ High - Medium Priority
- Add a new notification
- Add a chart
- Fix a bug
✅ Accept:
- Bug/Security fix
- Translations
- Adding notification providers
#### *️⃣ Requires one more reviewer
❌ Avoid:
- Large pull requests
- New big features
I do not have such knowledge to test it.
My long story here: https://www.reddit.com/r/UptimeKuma/comments/t1t6or/comment/hynyijx/
- Add k8s supports
### Recommended Pull Request Guideline
#### *️⃣ Low Priority
It changed my current workflow and require further studies.
- Change my release approach
1. Fork the project
1. Clone your fork repo to local
1. Create a new branch
1. Create an empty commit
`git commit -m "[empty commit] pull request for <YOUR TASK NAME>" --allow-empty`
1. Push to your fork repo
1. Create a pull request: https://github.com/louislam/uptime-kuma/compare
1. Write a proper description
1. Click "Change to draft"
#### ❌ Won't Merge
- Any breaking changes
- Duplicated pull request
- Buggy
- Existing logic is completely modified or deleted
@@ -84,7 +87,7 @@ I personally do not like something need to learn so much and need to config so m
- Node.js >= 14
- Git
- IDE that supports ESLint and EditorConfig (I am using Intellji Idea)
- IDE that supports ESLint and EditorConfig (I am using IntelliJ IDEA)
- A SQLite tool (SQLite Expert Personal is suggested)
## Install dependencies
@@ -111,9 +114,9 @@ express.js is just used for serving the frontend built files (index.html, .js an
- model/ (Object model, auto mapping to the database table name)
- modules/ (Modified 3rd-party modules)
- notification-providers/ (indivdual notification logic)
- notification-providers/ (individual notification logic)
- routers/ (Express Routers)
- scoket-handler (Socket.io Handlers)
- socket-handler (Socket.io Handlers)
- server.js (Server main logic)
## How to start the Frontend Dev Server
@@ -171,10 +174,64 @@ ncu -u -t patch
npm install
```
Since previously updating vite 2.5.10 to 2.6.0 broke the application completely, from now on, it should update patch release version only.
Since previously updating Vite 2.5.10 to 2.6.0 broke the application completely, from now on, it should update patch release version only.
Patch release = the third digit ([Semantic Versioning](https://semver.org/))
## Translations
Please read: https://github.com/louislam/uptime-kuma/tree/master/src/languages
## Wiki
Since there is no way to make a pull request to wiki's repo, I have set up another repo to do that.
https://github.com/louislam/uptime-kuma-wiki
## Maintainer
Check the latest issues and pull requests:
https://github.com/louislam/uptime-kuma/issues?q=sort%3Aupdated-desc
### Release Procedures
1. Draft a release note
2. Make sure the repo is cleared
3. `npm run release-final with env vars: `VERSION` and `GITHUB_TOKEN`
4. Wait until the `Press any key to continue`
5. `git push`
6. Publish the release note as 1.X.X
7. Press any key to continue
8. SSH to demo site server and update to 1.X.X
Checking:
- Check all tags is fine on https://hub.docker.com/r/louislam/uptime-kuma/tags
- Try the Docker image with tag 1.X.X (Clean install / amd64 / arm64 / armv7)
- Try clean installation with Node.js
### Release Beta Procedures
1. Draft a release note, check "This is a pre-release"
2. Make sure the repo is cleared
3. `npm run release-beta` with env vars: `VERSION` and `GITHUB_TOKEN`
4. Wait until the `Press any key to continue`
5. Publish the release note as 1.X.X-beta.X
6. Press any key to continue
### Release Wiki
#### Setup Repo
```bash
git clone https://github.com/louislam/uptime-kuma-wiki.git
cd uptime-kuma-wiki
git remote add production https://github.com/louislam/uptime-kuma.wiki.git
```
#### Push to Production Wiki
```bash
git pull
git push production master
```

View File

@@ -1,6 +1,7 @@
# Uptime Kuma
<a target="_blank" href="https://github.com/louislam/uptime-kuma"><img src="https://img.shields.io/github/stars/louislam/uptime-kuma" /></a> <a target="_blank" href="https://hub.docker.com/r/louislam/uptime-kuma"><img src="https://img.shields.io/docker/pulls/louislam/uptime-kuma" /></a> <a target="_blank" href="https://hub.docker.com/r/louislam/uptime-kuma"><img src="https://img.shields.io/docker/v/louislam/uptime-kuma/latest?label=docker%20image%20ver." /></a> <a target="_blank" href="https://github.com/louislam/uptime-kuma"><img src="https://img.shields.io/github/last-commit/louislam/uptime-kuma" /></a> <a target="_blank" href="https://opencollective.com/uptime-kuma"><img src="https://opencollective.com/uptime-kuma/total/badge.svg?label=Backers&color=brightgreen" /></a>
<a target="_blank" href="https://github.com/louislam/uptime-kuma"><img src="https://img.shields.io/github/stars/louislam/uptime-kuma" /></a> <a target="_blank" href="https://hub.docker.com/r/louislam/uptime-kuma"><img src="https://img.shields.io/docker/pulls/louislam/uptime-kuma" /></a> <a target="_blank" href="https://hub.docker.com/r/louislam/uptime-kuma"><img src="https://img.shields.io/docker/v/louislam/uptime-kuma/latest?label=docker%20image%20ver." /></a> <a target="_blank" href="https://github.com/louislam/uptime-kuma"><img src="https://img.shields.io/github/last-commit/louislam/uptime-kuma" /></a> <a target="_blank" href="https://opencollective.com/uptime-kuma"><img src="https://opencollective.com/uptime-kuma/total/badge.svg?label=Open%20Collective%20Backers&color=brightgreen" /></a>
[![GitHub Sponsors](https://img.shields.io/github/sponsors/louislam?label=GitHub%20Sponsors)](https://github.com/sponsors/louislam)
<div align="center" width="100%">
<img src="./public/icon.svg" width="128" alt="" />
@@ -16,13 +17,13 @@ Try it!
https://demo.uptime.kuma.pet
It is a temporary live demo, all data will be deleted after 10 minutes. The server is located at Tokyo, so if you live far from there it may affect your experience. I suggest that you should install and try it out for the best demo experience.
It is a temporary live demo, all data will be deleted after 10 minutes. The server is located in Tokyo, so if you live far from there, it may affect your experience. I suggest that you should install and try it out for the best demo experience.
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 / Push.
* Monitoring uptime for HTTP(s) / TCP / HTTP(s) Keyword / Ping / DNS Record / Push / Steam Game Server.
* 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/tree/master/src/components/notifications).
* 20 second intervals.
@@ -40,9 +41,11 @@ docker volume create uptime-kuma
docker run -d --restart=always -p 3001:3001 -v uptime-kuma:/app/data --name uptime-kuma louislam/uptime-kuma:1
```
⚠️ Please use a **local volume** only. Other types such as NFS are not supported.
Browse to http://localhost:3001 after starting.
### 💪🏻 Without Docker
### 💪🏻 Non-Docker
Required Tools: Node.js >= 14, git and pm2.
@@ -58,15 +61,21 @@ npm run setup
node server/server.js
# (Recommended) Option 2. Run in background using PM2
# Install PM2 if you don't have it: npm install pm2 -g
# Install PM2 if you don't have it:
npm install pm2 -g && pm2 install pm2-logrotate
# Start Server
pm2 start server/server.js --name uptime-kuma
# If you want to see the current console output
pm2 monit
```
Browse to http://localhost:3001 after starting.
### Advanced Installation
If you need more options or need to browse via a reserve proxy, please read:
If you need more options or need to browse via a reverse proxy, please read:
https://github.com/louislam/uptime-kuma/wiki/%F0%9F%94%A7-How-to-Install
@@ -86,6 +95,12 @@ Project Plan:
https://github.com/louislam/uptime-kuma/projects/1
## ❤️ Sponsors
Thank you so much! (GitHub Sponsors will be updated manually. OpenCollective sponsors will be updated automatically, the list will be cached by GitHub though. It may need some time to be updated)
<img src="https://uptime.kuma.pet/sponsors?v=6" alt />
## 🖼 More Screenshots
Light Mode:
@@ -106,7 +121,7 @@ Telegram Notification Sample:
## Motivation
* I was looking for a self-hosted monitoring tool like "Uptime Robot", but it is hard to find a suitable one. One of the close ones is statping. Unfortunately, it is not stable and unmaintained.
* I was looking for a self-hosted monitoring tool like "Uptime Robot", but it is hard to find a suitable one. One of the close ones is statping. Unfortunately, it is not stable and no longer maintained.
* Want to build a fancy UI.
* Learn Vue 3 and vite.js.
* Show the power of Bootstrap 5.
@@ -119,7 +134,7 @@ If you love this project, please consider giving me a ⭐.
### Issues Page
You can discuss or ask for help in [Issues](https://github.com/louislam/uptime-kuma/issues).
You can discuss or ask for help in [issues](https://github.com/louislam/uptime-kuma/issues).
### Subreddit
@@ -131,8 +146,8 @@ https://www.reddit.com/r/UptimeKuma/
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 translate Uptime Kuma into your language, 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.
Unfortunately, English proofreading is needed too because my grammar is not that great. Feel free to correct my grammar in this README, source code, or wiki.

View File

@@ -1,5 +1,11 @@
# Security Policy
## Reporting a Vulnerability
Please report security issues to uptime@kuma.pet.
Do not use the issue tracker or discuss it in the public as it will cause more damage.
## Supported Versions
Use this section to tell people about which versions of your project are
@@ -9,8 +15,8 @@ currently being supported with security updates.
| Version | Supported |
| ------- | ------------------ |
| 1.8.X | :white_check_mark: |
| <= 1.7.X | ❌ |
| 1.9.X | :white_check_mark: |
| <= 1.8.X | ❌ |
### Upgradable Docker Tags
@@ -23,9 +29,3 @@ currently being supported with security updates.
| debian | :white_check_mark: |
| alpine | :white_check_mark: |
| All other tags | ❌ |
## Reporting a Vulnerability
Please report security issues to uptime@kuma.pet.
Do not use the issue tracker or discuss it in the public as it will cause more damage.

33
config/jest-debug-env.js Normal file
View File

@@ -0,0 +1,33 @@
const PuppeteerEnvironment = require("jest-environment-puppeteer");
const util = require("util");
class DebugEnv extends PuppeteerEnvironment {
async handleTestEvent(event, state) {
const ignoredEvents = [
"setup",
"add_hook",
"start_describe_definition",
"add_test",
"finish_describe_definition",
"run_start",
"run_describe_start",
"test_start",
"hook_start",
"hook_success",
"test_fn_start",
"test_fn_success",
"test_done",
"run_describe_finish",
"run_finish",
"teardown",
"test_fn_failure",
];
if (!ignoredEvents.includes(event.name)) {
console.log(
new Date().toString() + ` Unhandled event [${event.name}] ` + util.inspect(event)
);
}
}
}
module.exports = DebugEnv;

View File

@@ -1,6 +1,20 @@
module.exports = {
"launch": {
"dumpio": true,
"slowMo": 500,
"headless": process.env.HEADLESS_TEST || false,
"userDataDir": "./data/test-chrome-profile",
args: [
"--disable-setuid-sandbox",
"--disable-gpu",
"--disable-dev-shm-usage",
"--no-default-browser-check",
"--no-experiments",
"--no-first-run",
"--no-pings",
"--no-sandbox",
"--no-zygote",
"--single-process",
],
}
};

View File

@@ -5,6 +5,7 @@ module.exports = {
"__DEV__": true
},
"testRegex": "./test/e2e.spec.js",
"testEnvironment": "./config/jest-debug-env.js",
"rootDir": "..",
"testTimeout": 30000,
};

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 user
ADD twofa_last_token VARCHAR(6);
COMMIT;

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;
ALTER TABLE monitor
ADD basic_auth_user TEXT default null;
ALTER TABLE monitor
ADD basic_auth_pass TEXT default null;
COMMIT;

View File

@@ -0,0 +1,18 @@
-- You should not modify if this have pushed to Github, unless it does serious wrong with the db.
BEGIN TRANSACTION;
CREATE TABLE [notification_sent_history] (
[id] INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
[type] VARCHAR(50) NOT NULL,
[monitor_id] INTEGER NOT NULL,
[days] INTEGER NOT NULL,
UNIQUE([type], [monitor_id], [days])
);
CREATE INDEX [good_index] ON [notification_sent_history] (
[type],
[monitor_id],
[days]
);
COMMIT;

23
db/patch-proxy.sql Normal file
View File

@@ -0,0 +1,23 @@
-- You should not modify if this have pushed to Github, unless it does serious wrong with the db.
BEGIN TRANSACTION;
CREATE TABLE proxy (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
user_id INT NOT NULL,
protocol VARCHAR(10) NOT NULL,
host VARCHAR(255) NOT NULL,
port SMALLINT NOT NULL,
auth BOOLEAN NOT NULL,
username VARCHAR(255) NULL,
password VARCHAR(255) NULL,
active BOOLEAN NOT NULL DEFAULT 1,
'default' BOOLEAN NOT NULL DEFAULT 0,
created_date DATETIME DEFAULT (DATETIME('now')) NOT NULL
);
ALTER TABLE monitor ADD COLUMN proxy_id INTEGER REFERENCES proxy(id);
CREATE INDEX proxy_id ON monitor (proxy_id);
CREATE INDEX proxy_user_id ON proxy (user_id);
COMMIT;

31
db/patch-status-page.sql Normal file
View File

@@ -0,0 +1,31 @@
-- You should not modify if this have pushed to Github, unless it does serious wrong with the db.
BEGIN TRANSACTION;
CREATE TABLE [status_page](
[id] INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
[slug] VARCHAR(255) NOT NULL UNIQUE,
[title] VARCHAR(255) NOT NULL,
[description] TEXT,
[icon] VARCHAR(255) NOT NULL,
[theme] VARCHAR(30) NOT NULL,
[published] BOOLEAN NOT NULL DEFAULT 1,
[search_engine_index] BOOLEAN NOT NULL DEFAULT 1,
[show_tags] BOOLEAN NOT NULL DEFAULT 0,
[password] VARCHAR,
[created_date] DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
[modified_date] DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE UNIQUE INDEX [slug] ON [status_page]([slug]);
CREATE TABLE [status_page_cname](
[id] INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
[status_page_id] INTEGER NOT NULL REFERENCES [status_page]([id]) ON DELETE CASCADE ON UPDATE CASCADE,
[domain] VARCHAR NOT NULL UNIQUE
);
ALTER TABLE incident ADD status_page_id INTEGER;
ALTER TABLE [group] ADD status_page_id INTEGER;
COMMIT;

View File

@@ -1,8 +1,8 @@
# DON'T UPDATE TO alpine3.13, 1.14, see #41.
FROM node:14-alpine3.12
FROM node:16-alpine3.12
WORKDIR /app
# Install apprise, iputils for non-root ping, setpriv
RUN apk add --no-cache iputils setpriv dumb-init python3 py3-cryptography py3-pip py3-six py3-yaml py3-click py3-markdown py3-requests py3-requests-oauthlib && \
pip3 --no-cache-dir install apprise && \
pip3 --no-cache-dir install apprise==0.9.7 && \
rm -rf /root/.cache

View File

@@ -1,12 +1,26 @@
# 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
FROM node:16-buster-slim
ARG TARGETPLATFORM
WORKDIR /app
# Install Curl
# Install Apprise, add sqlite3 cli for debugging in the future, iputils-ping for ping, util-linux for setpriv
# Stupid python3 and python3-pip actually install a lot of useless things into Debian, specific --no-install-recommends to skip them, make the base even smaller than alpine!
# Stupid python3 and python3-pip actually install a lot of useless things into Debian, specify --no-install-recommends to skip them, make the base even smaller than alpine!
RUN apt update && \
apt --yes --no-install-recommends install python3 python3-pip python3-cryptography python3-six python3-yaml python3-click python3-markdown python3-requests python3-requests-oauthlib \
sqlite3 iputils-ping util-linux dumb-init && \
pip3 --no-cache-dir install apprise && \
pip3 --no-cache-dir install apprise==0.9.7 && \
rm -rf /var/lib/apt/lists/*
# Install cloudflared
# dpkg --add-architecture arm: cloudflared do not provide armhf, this is workaround. Read more: https://github.com/cloudflare/cloudflared/issues/583
COPY extra/download-cloudflared.js ./extra/download-cloudflared.js
RUN node ./extra/download-cloudflared.js $TARGETPLATFORM && \
dpkg --add-architecture arm && \
apt update && \
apt --yes --no-install-recommends install ./cloudflared.deb && \
rm -rf /var/lib/apt/lists/* && \
rm -f cloudflared.deb

View File

@@ -4,9 +4,7 @@ WORKDIR /app
ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=1
COPY . .
RUN npm ci && \
npm run build && \
npm ci --production && \
RUN npm ci --production && \
chmod +x /app/extra/entrypoint.sh
@@ -22,23 +20,26 @@ HEALTHCHECK --interval=60s --timeout=30s --start-period=180s --retries=5 CMD nod
ENTRYPOINT ["/usr/bin/dumb-init", "--", "extra/entrypoint.sh"]
CMD ["node", "server/server.js"]
FROM release AS nightly
RUN npm run mark-as-nightly
# Upload the artifact to Github
FROM louislam/uptime-kuma:base-debian AS upload-artifact
WORKDIR /
RUN apt update && \
apt --yes install curl file
COPY --from=build /app /app
ARG VERSION
ARG GITHUB_TOKEN
ARG TARGETARCH
ARG PLATFORM=debian
ARG VERSION=1.9.0
ARG FILE=$PLATFORM-$TARGETARCH-$VERSION.tar.gz
ARG DIST=dist.tar.gz
COPY --from=build /app /app
RUN chmod +x /app/extra/upload-github-release-asset.sh
# Full Build

View File

@@ -4,9 +4,7 @@ WORKDIR /app
ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=1
COPY . .
RUN npm ci && \
npm run build && \
npm ci --production && \
RUN npm ci --production && \
chmod +x /app/extra/entrypoint.sh
@@ -22,5 +20,6 @@ HEALTHCHECK --interval=60s --timeout=30s --start-period=180s --retries=5 CMD nod
ENTRYPOINT ["/usr/bin/dumb-init", "--", "extra/entrypoint.sh"]
CMD ["node", "server/server.js"]
FROM release AS nightly
RUN npm run mark-as-nightly

View File

@@ -0,0 +1,71 @@
const pkg = require("../../package.json");
const fs = require("fs");
const child_process = require("child_process");
const util = require("../../src/util");
util.polyfill();
const oldVersion = pkg.version;
const version = process.env.VERSION;
console.log("Beta Version: " + version);
if (!version || !version.includes("-beta.")) {
console.error("invalid version, beta version only");
process.exit(1);
}
const exists = tagExists(version);
if (! exists) {
// Process package.json
pkg.version = version;
fs.writeFileSync("package.json", JSON.stringify(pkg, null, 4) + "\n");
commit(version);
tag(version);
} else {
console.log("version tag exists, please delete the tag or use another tag");
process.exit(1);
}
function commit(version) {
let msg = "Update to " + version;
let res = child_process.spawnSync("git", ["commit", "-m", msg, "-a"]);
let stdout = res.stdout.toString().trim();
console.log(stdout);
if (stdout.includes("no changes added to commit")) {
throw new Error("commit error");
}
res = child_process.spawnSync("git", ["push", "origin", "master"]);
console.log(res.stdout.toString().trim());
}
function tag(version) {
let res = child_process.spawnSync("git", ["tag", version]);
console.log(res.stdout.toString().trim());
res = child_process.spawnSync("git", ["push", "origin", version]);
console.log(res.stdout.toString().trim());
}
function tagExists(version) {
if (! version) {
throw new Error("invalid version");
}
let res = child_process.spawnSync("git", ["tag", "-l", version]);
return res.stdout.toString().trim() === version;
}
function safeDelete(dir) {
if (fs.existsSync(dir)) {
fs.rmdirSync(dir, {
recursive: true,
});
}
}

View File

@@ -0,0 +1,57 @@
const github = require("@actions/github");
(async () => {
try {
const token = process.argv[2];
const issueNumber = process.argv[3];
const username = process.argv[4];
const client = github.getOctokit(token).rest;
const issue = {
owner: "louislam",
repo: "uptime-kuma",
number: issueNumber,
};
const labels = (
await client.issues.listLabelsOnIssue({
owner: issue.owner,
repo: issue.repo,
issue_number: issue.number
})
).data.map(({ name }) => name);
if (labels.length === 0) {
console.log("Bad format here");
await client.issues.addLabels({
owner: issue.owner,
repo: issue.repo,
issue_number: issue.number,
labels: ["invalid-format"]
});
// Add the issue closing comment
await client.issues.createComment({
owner: issue.owner,
repo: issue.repo,
issue_number: issue.number,
body: `@${username}: Hello! :wave:\n\nThis issue is being automatically closed because it does not follow the issue template. Please DO NOT open a blank issue.`
});
// Close the issue
await client.issues.update({
owner: issue.owner,
repo: issue.repo,
issue_number: issue.number,
state: "closed"
});
} else {
console.log("Pass!");
}
} catch (e) {
console.log(e);
}
})();

View File

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

View File

@@ -4,6 +4,7 @@ const tar = require("tar");
const packageJSON = require("../package.json");
const fs = require("fs");
const rmSync = require("./fs-rmSync.js");
const version = packageJSON.version;
const filename = "dist.tar.gz";
@@ -21,7 +22,7 @@ function download(url) {
if (fs.existsSync("./dist")) {
if (fs.existsSync("./dist-backup")) {
fs.rmdirSync("./dist-backup", {
rmSync("./dist-backup", {
recursive: true
});
}
@@ -34,9 +35,11 @@ function download(url) {
});
tarStream.on("close", () => {
fs.rmdirSync("./dist-backup", {
recursive: true
});
if (fs.existsSync("./dist-backup")) {
rmSync("./dist-backup", {
recursive: true
});
}
console.log("Done");
});
@@ -44,7 +47,7 @@ function download(url) {
if (fs.existsSync("./dist-backup")) {
fs.renameSync("./dist-backup", "./dist");
}
console.log("Done");
console.error("Error from tarStream");
});
response.pipe(tarStream);

19
extra/env2arg.js Normal file
View File

@@ -0,0 +1,19 @@
#!/usr/bin/env node
const childProcess = require("child_process");
let env = process.env;
let cmd = process.argv[2];
let args = process.argv.slice(3);
let replacedArgs = [];
for (let arg of args) {
for (let key in env) {
arg = arg.replaceAll(`$${key}`, env[key]);
}
replacedArgs.push(arg);
}
let child = childProcess.spawn(cmd, replacedArgs);
child.stdout.pipe(process.stdout);
child.stderr.pipe(process.stderr);

20
extra/fs-rmSync.js Normal file
View File

@@ -0,0 +1,20 @@
const fs = require("fs");
/**
* Detect if `fs.rmSync` is available
* to avoid the runtime deprecation warning triggered for using `fs.rmdirSync` with `{ recursive: true }` in Node.js v16,
* or the `recursive` property removing completely in the future Node.js version.
* See the link below.
* @link https://nodejs.org/docs/latest-v16.x/api/deprecations.html#dep0147-fsrmdirpath--recursive-true-
* @param {fs.PathLike} path Valid types for path values in "fs".
* @param {fs.RmDirOptions} [options] options for `fs.rmdirSync`, if `fs.rmSync` is available and property `recursive` is true, it will automatically have property `force` with value `true`.
*/
const rmSync = (path, options) => {
if (typeof fs.rmSync === "function") {
if (options.recursive) {
options.force = true;
}
return fs.rmSync(path, options);
}
return fs.rmdirSync(path, options);
};
module.exports = rmSync;

View File

@@ -1,25 +1,41 @@
/*
* This script should be run after a period of time (180s), because the server may need some time to prepare.
*/
const { FBSD } = require("../server/util-server");
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
let client;
if (process.env.SSL_KEY && process.env.SSL_CERT) {
const sslKey = process.env.UPTIME_KUMA_SSL_KEY || process.env.SSL_KEY || undefined;
const sslCert = process.env.UPTIME_KUMA_SSL_CERT || process.env.SSL_CERT || undefined;
if (sslKey && sslCert) {
client = require("https");
} else {
client = require("http");
}
// If host is omitted, the server will accept connections on the unspecified IPv6 address (::) when IPv6 is available and the unspecified IPv4 address (0.0.0.0) otherwise.
// Dual-stack support for (::)
let hostname = process.env.UPTIME_KUMA_HOST;
// Also read HOST if not *BSD, as HOST is a system environment variable in FreeBSD
if (!hostname && !FBSD) {
hostname = process.env.HOST;
}
const port = parseInt(process.env.UPTIME_KUMA_PORT || process.env.PORT || 3001);
let options = {
host: process.env.HOST || "127.0.0.1",
port: parseInt(process.env.PORT) || 3001,
host: hostname || "127.0.0.1",
port: port,
timeout: 28 * 1000,
};
let request = client.request(options, (res) => {
console.log(`Health Check OK [Res Code: ${res.statusCode}]`);
if (res.statusCode === 200) {
if (res.statusCode === 302) {
process.exit(0);
} else {
process.exit(1);

View File

@@ -189,7 +189,7 @@ if (type == "local") {
bash("check=$(pm2 --version)");
if (check == "") {
println("Installing PM2");
bash("npm install pm2 -g");
bash("npm install pm2 -g && pm2 install pm2-logrotate");
bash("pm2 startup");
}

6
extra/press-any-key.js Normal file
View File

@@ -0,0 +1,6 @@
console.log("Git Push and Publish the release note on github, then press any key to continue");
process.stdin.setRawMode(true);
process.stdin.resume();
process.stdin.on("data", process.exit.bind(process, 0));

60
extra/remove-2fa.js Normal file
View File

@@ -0,0 +1,60 @@
console.log("== Uptime Kuma Remove 2FA Tool ==");
console.log("Loading the database");
const Database = require("../server/database");
const { R } = require("redbean-node");
const readline = require("readline");
const TwoFA = require("../server/2fa");
const args = require("args-parser")(process.argv);
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
const main = async () => {
Database.init(args);
await Database.connect();
try {
// No need to actually reset the password for testing, just make sure no connection problem. It is ok for now.
if (!process.env.TEST_BACKEND) {
const user = await R.findOne("user");
if (! user) {
throw new Error("user not found, have you installed?");
}
console.log("Found user: " + user.username);
let ans = await question("Are you sure want to remove 2FA? [y/N]");
if (ans.toLowerCase() === "y") {
await TwoFA.disable2FA(user.id);
console.log("2FA has been removed successfully.");
}
}
} catch (e) {
console.error("Error: " + e.message);
}
await Database.close();
rl.close();
console.log("Finished.");
};
function question(question) {
return new Promise((resolve) => {
rl.question(question, (answer) => {
resolve(answer);
});
});
}
if (!process.env.TEST_BACKEND) {
main();
}
module.exports = {
main,
};

View File

@@ -3,6 +3,7 @@
import fs from "fs";
import path from "path";
import util from "util";
import rmSync from "../fs-rmSync.js";
// https://stackoverflow.com/questions/13786160/copy-folder-recursively-in-node-js
/**
@@ -30,7 +31,7 @@ console.log("Arguments:", process.argv);
const baseLangCode = process.argv[2] || "en";
console.log("Base Lang: " + baseLangCode);
if (fs.existsSync("./languages")) {
fs.rmdirSync("./languages", { recursive: true });
rmSync("./languages", { recursive: true });
}
copyRecursiveSync("../../src/languages", "./languages");
@@ -40,7 +41,7 @@ const files = fs.readdirSync("./languages");
console.log("Files:", files);
for (const file of files) {
if (!file.endsWith(".js")) {
if (! file.endsWith(".js")) {
console.log("Skipping " + file);
continue;
}
@@ -82,5 +83,5 @@ for (const file of files) {
fs.writeFileSync(`../../src/languages/${file}`, code);
}
fs.rmdirSync("./languages", { recursive: true });
rmSync("./languages", { recursive: true });
console.log("Done. Fixing formatting by ESLint...");

View File

@@ -1,14 +1,13 @@
const pkg = require("../package.json");
const fs = require("fs");
const rmSync = require("./fs-rmSync.js");
const child_process = require("child_process");
const util = require("../src/util");
util.polyfill();
const oldVersion = pkg.version;
const newVersion = process.argv[2];
const newVersion = process.env.VERSION;
console.log("Old Version: " + oldVersion);
console.log("New Version: " + newVersion);
if (! newVersion) {
@@ -22,23 +21,20 @@ if (! exists) {
// Process package.json
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);
// Replace the version: https://regex101.com/r/hmj2Bc/1
pkg.scripts.setup = pkg.scripts.setup.replace(/(git checkout )([^\s]+)/, `$1${newVersion}`);
fs.writeFileSync("package.json", JSON.stringify(pkg, null, 4) + "\n");
commit(newVersion);
tag(newVersion);
updateWiki(oldVersion, newVersion);
} else {
console.log("version exists");
}
function commit(version) {
let msg = "update to " + version;
let msg = "Update to " + version;
let res = child_process.spawnSync("git", ["commit", "-m", msg, "-a"]);
let stdout = res.stdout.toString().trim();
@@ -63,38 +59,3 @@ function tagExists(version) {
return res.stdout.toString().trim() === version;
}
function updateWiki(oldVersion, newVersion) {
const wikiDir = "./tmp/wiki";
const howToUpdateFilename = "./tmp/wiki/🆙-How-to-Update.md";
safeDelete(wikiDir);
child_process.spawnSync("git", ["clone", "https://github.com/louislam/uptime-kuma.wiki.git", wikiDir]);
let content = fs.readFileSync(howToUpdateFilename).toString();
content = content.replaceAll(`git checkout ${oldVersion}`, `git checkout ${newVersion}`);
fs.writeFileSync(howToUpdateFilename, content);
child_process.spawnSync("git", ["add", "-A"], {
cwd: wikiDir,
});
child_process.spawnSync("git", ["commit", "-m", `Update to ${newVersion} from ${oldVersion}`], {
cwd: wikiDir,
});
console.log("Pushing to Github");
child_process.spawnSync("git", ["push"], {
cwd: wikiDir,
});
safeDelete(wikiDir);
}
function safeDelete(dir) {
if (fs.existsSync(dir)) {
fs.rmdirSync(dir, {
recursive: true,
});
}
}

View File

@@ -0,0 +1,48 @@
const child_process = require("child_process");
const fs = require("fs");
const newVersion = process.env.VERSION;
if (!newVersion) {
console.log("Missing version");
process.exit(1);
}
updateWiki(newVersion);
function updateWiki(newVersion) {
const wikiDir = "./tmp/wiki";
const howToUpdateFilename = "./tmp/wiki/🆙-How-to-Update.md";
safeDelete(wikiDir);
child_process.spawnSync("git", ["clone", "https://github.com/louislam/uptime-kuma.wiki.git", wikiDir]);
let content = fs.readFileSync(howToUpdateFilename).toString();
// Replace the version: https://regex101.com/r/hmj2Bc/1
content = content.replace(/(git checkout )([^\s]+)/, `$1${newVersion}`);
fs.writeFileSync(howToUpdateFilename, content);
child_process.spawnSync("git", ["add", "-A"], {
cwd: wikiDir,
});
child_process.spawnSync("git", ["commit", "-m", `Update to ${newVersion}`], {
cwd: wikiDir,
});
console.log("Pushing to Github");
child_process.spawnSync("git", ["push"], {
cwd: wikiDir,
});
safeDelete(wikiDir);
}
function safeDelete(dir) {
if (fs.existsSync(dir)) {
fs.rmdirSync(dir, {
recursive: true,
});
}
}

View File

@@ -5,7 +5,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">
<link rel="icon" type="image/svg+xml" href="/icon.svg" />
<link rel="manifest" href="manifest.json" />
<link rel="manifest" href="/manifest.json" />
<meta name="theme-color" id="theme-color" content="" />
<meta name="description" content="Uptime Kuma monitoring tool" />
<title>Uptime Kuma</title>

View File

@@ -159,7 +159,7 @@ fi
check=$(pm2 --version)
if [ "$check" == "" ]; then
"echo" "-e" "Installing PM2"
npm install pm2 -g
npm install pm2 -g && pm2 install pm2-logrotate
pm2 startup
fi
mkdir -p $installPath

18917
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,13 +1,13 @@
{
"name": "uptime-kuma",
"version": "1.9.2",
"version": "1.14.0-beta.1",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/louislam/uptime-kuma.git"
},
"engines": {
"node": "14.*"
"node": "14.* || >=16.*"
},
"scripts": {
"install-legacy": "npm install --legacy-peer-deps",
@@ -22,25 +22,25 @@
"build": "vite build --config ./config/vite.config.js",
"test": "node test/prepare-test-server.js && node server/server.js --port=3002 --data-dir=./data/test/ --test",
"test-with-build": "npm run build && npm test",
"jest": "node test/prepare-jest.js && npm run jest-frontend && npm run jest-backend && jest --config=./config/jest.config.js",
"jest": "node test/prepare-jest.js && npm run jest-frontend && npm run jest-backend",
"jest-frontend": "cross-env TEST_FRONTEND=1 jest --config=./config/jest-frontend.config.js",
"jest-backend": "cross-env TEST_BACKEND=1 jest --config=./config/jest-backend.config.js",
"tsc": "tsc",
"vite-preview-dist": "vite preview --host --config ./config/vite.config.js",
"build-docker": "npm run build-docker-debian && npm run build-docker-alpine",
"build-docker": "npm run build && npm run build-docker-debian && npm run build-docker-alpine",
"build-docker-alpine-base": "docker buildx build -f docker/alpine-base.dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:base-alpine . --push",
"build-docker-debian-base": "docker buildx build -f docker/debian-base.dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:base-debian . --push",
"build-docker-alpine": "docker buildx build -f docker/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.9.2-alpine --target release . --push",
"build-docker-debian": "docker buildx build -f docker/dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma -t louislam/uptime-kuma:1 -t louislam/uptime-kuma:1.9.2 -t louislam/uptime-kuma:debian -t louislam/uptime-kuma:1-debian -t louislam/uptime-kuma:1.9.2-debian --target release . --push",
"build-docker-nightly": "docker buildx build -f docker/dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:nightly --target nightly . --push",
"build-docker-alpine": "node ./extra/env2arg.js docker buildx build -f docker/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:$VERSION-alpine --target release . --push",
"build-docker-debian": "node ./extra/env2arg.js docker buildx build -f docker/dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma -t louislam/uptime-kuma:1 -t louislam/uptime-kuma:$VERSION -t louislam/uptime-kuma:debian -t louislam/uptime-kuma:1-debian -t louislam/uptime-kuma:$VERSION-debian --target release . --push",
"build-docker-nightly": "npm run build && docker buildx build -f docker/dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:nightly --target nightly . --push",
"build-docker-nightly-alpine": "docker buildx build -f docker/dockerfile-alpine --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:nightly-alpine --target nightly . --push",
"build-docker-nightly-amd64": "docker buildx build -f docker/dockerfile --platform linux/amd64 -t louislam/uptime-kuma:nightly-amd64 --target nightly . --push --progress plain",
"upload-artifacts": "docker buildx build -f docker/dockerfile --platform linux/amd64 -t louislam/uptime-kuma:upload-artifact --build-arg GITHUB_TOKEN --target upload-artifact . --progress plain",
"setup": "git checkout 1.9.2 && npm ci --production && npm run download-dist",
"upload-artifacts": "docker buildx build -f docker/dockerfile --platform linux/amd64 -t louislam/uptime-kuma:upload-artifact --build-arg VERSION --build-arg GITHUB_TOKEN --target upload-artifact . --progress plain",
"setup": "git checkout 1.13.1 && npm ci --production && npm run download-dist",
"download-dist": "node extra/download-dist.js",
"update-version": "node extra/update-version.js",
"mark-as-nightly": "node extra/mark-as-nightly.js",
"reset-password": "node extra/reset-password.js",
"remove-2fa": "node extra/remove-2fa.js",
"compile-install-script": "@powershell -NoProfile -ExecutionPolicy Unrestricted -Command ./extra/compile-install-script.ps1",
"test-install-script-centos7": "npm run compile-install-script && docker build --progress plain -f test/test_install_script/centos7.dockerfile .",
"test-install-script-alpine3": "npm run compile-install-script && docker build --progress plain -f test/test_install_script/alpine3.dockerfile .",
@@ -49,80 +49,94 @@
"test-nodejs16": "docker build --progress plain -f test/ubuntu-nodejs16.dockerfile .",
"simple-dns-server": "node extra/simple-dns-server.js",
"update-language-files-with-base-lang": "cd extra/update-language-files && node index.js %npm_config_base_lang% && eslint ../../src/languages/**.js --fix",
"update-language-files": "cd extra/update-language-files && node index.js && eslint ../../src/languages/**.js --fix"
"update-language-files": "cd extra/update-language-files && node index.js && eslint ../../src/languages/**.js --fix",
"ncu-patch": "npm-check-updates -u -t patch",
"release-final": "node extra/update-version.js && npm run build-docker && node ./extra/press-any-key.js && npm run upload-artifacts && node ./extra/update-wiki-version.js",
"release-beta": "node extra/beta/update-version.js && npm run build && node ./extra/env2arg.js docker buildx build -f docker/dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:$VERSION -t louislam/uptime-kuma:beta . --target release --push && node ./extra/press-any-key.js && npm run upload-artifacts",
"git-remove-tag": "git tag -d"
},
"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/sqlite3": "~6.0.0",
"@fortawesome/vue-fontawesome": "~3.0.0-5",
"@louislam/sqlite3": "~6.0.1",
"@popperjs/core": "~2.10.2",
"args-parser": "~1.3.0",
"axios": "~0.21.4",
"axios": "~0.26.1",
"bcryptjs": "~2.4.3",
"bootstrap": "~5.1.1",
"bootstrap": "5.1.3",
"bree": "~7.1.5",
"chardet": "^1.3.0",
"bree": "~6.3.1",
"chart.js": "~3.5.1",
"chart.js": "~3.6.2",
"chartjs-adapter-dayjs": "~1.0.0",
"check-password-strength": "^2.0.5",
"command-exists": "~1.2.9",
"compare-versions": "~3.6.0",
"dayjs": "~1.10.7",
"express": "~4.17.1",
"express-basic-auth": "~1.2.0",
"dayjs": "~1.10.8",
"express": "~4.17.3",
"express-basic-auth": "~1.2.1",
"favico.js": "^0.3.10",
"form-data": "~4.0.0",
"http-graceful-shutdown": "~3.1.4",
"http-graceful-shutdown": "~3.1.7",
"http-proxy-agent": "^5.0.0",
"https-proxy-agent": "^5.0.0",
"iconv-lite": "^0.6.3",
"jsonwebtoken": "~8.5.1",
"jwt-decode": "^3.1.2",
"limiter": "^2.1.0",
"node-cloudflared-tunnel": "~1.0.9",
"nodemailer": "~6.6.5",
"notp": "~2.0.3",
"password-hash": "~1.2.2",
"postcss-rtlcss": "~3.4.1",
"postcss-scss": "~4.0.1",
"postcss-scss": "~4.0.3",
"prom-client": "~13.2.0",
"prometheus-api-metrics": "~3.2.0",
"qrcode": "~1.4.4",
"redbean-node": "0.1.2",
"socket.io": "~4.2.0",
"socket.io-client": "~4.2.0",
"prometheus-api-metrics": "~3.2.1",
"qrcode": "~1.5.0",
"redbean-node": "0.1.3",
"socket.io": "~4.4.1",
"socket.io-client": "~4.4.1",
"socks-proxy-agent": "^6.1.1",
"tar": "^6.1.11",
"tcp-ping": "~0.1.1",
"thirty-two": "~1.0.2",
"timezones-list": "~3.0.1",
"v-pagination-3": "~0.1.6",
"v-pagination-3": "~0.1.7",
"vue": "next",
"vue-chart-3": "~0.5.8",
"vue-chart-3": "3.0.9",
"vue-confirm-dialog": "~1.0.2",
"vue-contenteditable": "~3.0.4",
"vue-i18n": "~9.1.9",
"vue-image-crop-upload": "~3.0.3",
"vue-multiselect": "~3.0.0-alpha.2",
"vue-qrcode": "~1.0.0",
"vue-router": "~4.0.11",
"vue-toastification": "~2.0.0-rc.1",
"vue-router": "~4.0.14",
"vue-toastification": "~2.0.0-rc.5",
"vuedraggable": "~4.1.0"
},
"devDependencies": {
"@babel/eslint-parser": "~7.15.7",
"@actions/github": "~5.0.1",
"@babel/eslint-parser": "~7.15.8",
"@babel/preset-env": "^7.15.8",
"@types/bootstrap": "~5.1.6",
"@vitejs/plugin-legacy": "~1.6.1",
"@vitejs/plugin-vue": "~1.9.2",
"@vue/compiler-sfc": "~3.2.19",
"@types/bootstrap": "~5.1.9",
"@vitejs/plugin-legacy": "~1.6.4",
"@vitejs/plugin-vue": "~1.9.4",
"@vue/compiler-sfc": "~3.2.31",
"babel-plugin-rewire": "~1.2.0",
"core-js": "~3.18.1",
"core-js": "~3.18.3",
"cross-env": "~7.0.3",
"dns2": "~2.0.1",
"eslint": "~7.32.0",
"eslint-plugin-vue": "~7.18.0",
"jest": "~27.2.4",
"jest-puppeteer": "~6.0.0",
"puppeteer": "~10.4.0",
"jest": "~27.2.5",
"jest-puppeteer": "~6.0.3",
"npm-check-updates": "^12.5.5",
"puppeteer": "~13.1.3",
"sass": "~1.42.1",
"stylelint": "~13.13.1",
"stylelint-config-standard": "~22.0.0",
"typescript": "~4.4.3",
"vite": "~2.6.4"
"stylelint": "~14.2.0",
"stylelint-config-standard": "~24.0.0",
"typescript": "~4.4.4",
"vite": "~2.6.14"
}
}

14
server/2fa.js Normal file
View File

@@ -0,0 +1,14 @@
const { checkLogin } = require("./util-server");
const { R } = require("redbean-node");
class TwoFA {
static async disable2FA(userID) {
return await R.exec("UPDATE `user` SET twofa_status = 0 WHERE id = ? ", [
userID,
]);
}
}
module.exports = TwoFA;

View File

@@ -1,8 +1,9 @@
const basicAuth = require("express-basic-auth")
const basicAuth = require("express-basic-auth");
const passwordHash = require("./password-hash");
const { R } = require("redbean-node");
const { setting } = require("./util-server");
const { debug } = require("../src/util");
const { loginRateLimiter } = require("./rate-limiter");
/**
*
@@ -11,9 +12,13 @@ const { debug } = require("../src/util");
* @returns {Promise<Bean|null>}
*/
exports.login = async function (username, password) {
if (typeof username !== "string" || typeof password !== "string") {
return null;
}
let user = await R.findOne("user", " username = ? AND active = 1 ", [
username,
])
]);
if (user && passwordHash.verify(password, user.password)) {
// Upgrade the hash to bcrypt
@@ -27,25 +32,37 @@ exports.login = async function (username, password) {
}
return null;
}
};
function myAuthorizer(username, password, callback) {
setting("disableAuth").then((result) => {
if (result) {
callback(null, true)
} else {
// Login Rate Limit
loginRateLimiter.pass(null, 0).then((pass) => {
if (pass) {
exports.login(username, password).then((user) => {
callback(null, user != null)
})
}
})
callback(null, user != null);
if (user == null) {
loginRateLimiter.removeTokens(1);
}
});
} else {
callback(null, false);
}
});
}
exports.basicAuth = basicAuth({
authorizer: myAuthorizer,
authorizeAsync: true,
challenge: true,
});
exports.basicAuth = async function (req, res, next) {
const middleware = basicAuth({
authorizer: myAuthorizer,
authorizeAsync: true,
challenge: true,
});
const disabledAuth = await setting("disableAuth");
if (!disabledAuth) {
middleware(req, res, next);
} else {
next();
}
};

View File

@@ -1,5 +1,6 @@
const { setSetting } = require("./util-server");
const { setSetting, setting } = require("./util-server");
const axios = require("axios");
const compareVersions = require("compare-versions");
exports.version = require("../package.json").version;
exports.latestVersion = null;
@@ -9,18 +10,30 @@ let interval;
exports.startInterval = () => {
let check = async () => {
try {
const res = await axios.get("https://raw.githubusercontent.com/louislam/uptime-kuma/master/package.json");
if (typeof res.data === "string") {
res.data = JSON.parse(res.data);
}
const res = await axios.get("https://uptime.kuma.pet/version");
// For debug
if (process.env.TEST_CHECK_VERSION === "1") {
res.data.version = "1000.0.0";
res.data.slow = "1000.0.0";
}
if (!await setting("checkUpdate")) {
return;
}
let checkBeta = await setting("checkBeta");
if (checkBeta && res.data.beta) {
if (compareVersions.compare(res.data.beta, res.data.beta, ">")) {
exports.latestVersion = res.data.beta;
return;
}
}
if (res.data.slow) {
exports.latestVersion = res.data.slow;
}
exports.latestVersion = res.data.version;
} catch (_) { }
};

View File

@@ -83,6 +83,23 @@ async function sendImportantHeartbeatList(socket, monitorID, toUser = false, ove
}
/**
* Delivers proxy list
*
* @param socket
* @return {Promise<Bean[]>}
*/
async function sendProxyList(socket) {
const timeLogger = new TimeLogger();
const list = await R.find("proxy", " user_id = ? ", [socket.userID]);
io.to(socket.userID).emit("proxyList", list.map(bean => bean.export()));
timeLogger.print("Send Proxy List");
return list;
}
async function sendInfo(socket) {
socket.emit("info", {
version: checkVersion.version,
@@ -95,6 +112,6 @@ module.exports = {
sendNotificationList,
sendImportantHeartbeatList,
sendHeartbeatList,
sendInfo
sendProxyList,
sendInfo,
};

View File

@@ -50,6 +50,11 @@ class Database {
"patch-group-table.sql": true,
"patch-monitor-push_token.sql": true,
"patch-http-monitor-method-body-and-headers.sql": true,
"patch-2fa-invalidate-used-token.sql": true,
"patch-notification_sent_history.sql": true,
"patch-monitor-basic-auth.sql": true,
"patch-status-page.sql": true,
"patch-proxy.sql": true,
}
/**
@@ -77,7 +82,7 @@ class Database {
console.log(`Data Dir: ${Database.dataDir}`);
}
static async connect() {
static async connect(testMode = false) {
const acquireConnectionTimeout = 120 * 1000;
const Dialect = require("knex/lib/dialects/sqlite3/index.js");
@@ -110,9 +115,15 @@ class Database {
await R.autoloadModels("./server/model");
await R.exec("PRAGMA foreign_keys = ON");
// Change to WAL
await R.exec("PRAGMA journal_mode = WAL");
if (testMode) {
// Change to MEMORY
await R.exec("PRAGMA journal_mode = MEMORY");
} else {
// Change to WAL
await R.exec("PRAGMA journal_mode = WAL");
}
await R.exec("PRAGMA cache_size = -12000");
await R.exec("PRAGMA auto_vacuum = FULL");
console.log("SQLite config:");
console.log(await R.getAll("PRAGMA journal_mode"));
@@ -131,7 +142,7 @@ class Database {
console.info("Latest database version: " + this.latestVersion);
if (version === this.latestVersion) {
console.info("Database no need to patch");
console.info("Database patch not needed");
} else if (version > this.latestVersion) {
console.info("Warning: Database version is newer than expected");
} else {
@@ -152,8 +163,8 @@ class Database {
await Database.close();
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");
console.error("Start Uptime-Kuma failed due to issue patching the database");
console.error("Please submit a bug report if you still encounter the problem after restart: https://github.com/louislam/uptime-kuma/issues");
this.restore();
process.exit(1);
@@ -161,6 +172,7 @@ class Database {
}
await this.patch2();
await this.migrateNewStatusPage();
}
/**
@@ -191,7 +203,7 @@ class Database {
await Database.close();
console.error(ex);
console.error("Start Uptime-Kuma failed due to patch db failed");
console.error("Start Uptime-Kuma failed due to issue patching the database");
console.error("Please submit the bug report if you still encounter the problem after restart: https://github.com/louislam/uptime-kuma/issues");
this.restore();
@@ -202,6 +214,74 @@ class Database {
await setSetting("databasePatchedFiles", databasePatchedFiles);
}
/**
* Migrate status page value in setting to "status_page" table
* @returns {Promise<void>}
*/
static async migrateNewStatusPage() {
// Fix 1.13.0 empty slug bug
await R.exec("UPDATE status_page SET slug = 'empty-slug-recover' WHERE TRIM(slug) = ''");
let title = await setting("title");
if (title) {
console.log("Migrating Status Page");
let statusPageCheck = await R.findOne("status_page", " slug = 'default' ");
if (statusPageCheck !== null) {
console.log("Migrating Status Page - Skip, default slug record is already existing");
return;
}
let statusPage = R.dispense("status_page");
statusPage.slug = "default";
statusPage.title = title;
statusPage.description = await setting("description");
statusPage.icon = await setting("icon");
statusPage.theme = await setting("statusPageTheme");
statusPage.published = !!await setting("statusPagePublished");
statusPage.search_engine_index = !!await setting("searchEngineIndex");
statusPage.show_tags = !!await setting("statusPageTags");
statusPage.password = null;
if (!statusPage.title) {
statusPage.title = "My Status Page";
}
if (!statusPage.icon) {
statusPage.icon = "";
}
if (!statusPage.theme) {
statusPage.theme = "light";
}
let id = await R.store(statusPage);
await R.exec("UPDATE incident SET status_page_id = ? WHERE status_page_id IS NULL", [
id
]);
await R.exec("UPDATE [group] SET status_page_id = ? WHERE status_page_id IS NULL", [
id
]);
await R.exec("DELETE FROM setting WHERE type = 'statusPage'");
// Migrate Entry Page if it is status page
let entryPage = await setting("entryPage");
if (entryPage === "statusPage") {
await setSetting("entryPage", "statusPage-default", "general");
}
console.log("Migrating Status Page - Done");
}
}
/**
* Used it patch2() only
* @param sqlFilename
@@ -232,7 +312,7 @@ class Database {
this.patched = true;
await this.importSQLFile("./db/" + sqlFilename);
databasePatchedFiles[sqlFilename] = true;
console.log(sqlFilename + " is patched successfully");
console.log(sqlFilename + " was patched successfully");
} else {
debug(sqlFilename + " is already patched, skip");
@@ -287,7 +367,7 @@ class Database {
};
process.addListener("unhandledRejection", listener);
console.log("Closing DB");
console.log("Closing the database");
while (true) {
Database.noReject = true;
@@ -297,7 +377,7 @@ class Database {
if (Database.noReject) {
break;
} else {
console.log("Waiting to close the db");
console.log("Waiting to close the database");
}
}
console.log("SQLite closed");
@@ -312,7 +392,7 @@ class Database {
*/
static backup(version) {
if (! this.backupPath) {
console.info("Backup the db");
console.info("Backing up the database");
this.backupPath = this.dataDir + "kuma.db.bak" + version;
fs.copyFileSync(Database.path, this.backupPath);
@@ -335,7 +415,7 @@ class Database {
*/
static restore() {
if (this.backupPath) {
console.error("Patch db failed!!! Restoring the backup");
console.error("Patching the database failed!!! Restoring the backup");
const shmPath = Database.path + "-shm";
const walPath = Database.path + "-wal";
@@ -354,7 +434,7 @@ class Database {
fs.unlinkSync(walPath);
}
} catch (e) {
console.log("Restore failed, you may need to restore the backup manually");
console.log("Restore failed; you may need to restore the backup manually");
process.exit(1);
}
@@ -373,6 +453,17 @@ class Database {
console.log("Nothing to restore");
}
}
static getSize() {
debug("Database.getSize()");
let stats = fs.statSync(Database.path);
debug(stats);
return stats.size;
}
static async shrink() {
await R.exec("VACUUM");
}
}
module.exports = Database;

View File

@@ -6,7 +6,7 @@ const jobs = [
{
name: "clear-old-data",
interval: "at 03:14",
}
},
];
const initBackgroundJobs = function (args) {

View File

@@ -3,12 +3,12 @@ const { R } = require("redbean-node");
class Group extends BeanModel {
async toPublicJSON() {
async toPublicJSON(showTags = false) {
let monitorBeanList = await this.getMonitorList();
let monitorList = [];
for (let bean of monitorBeanList) {
monitorList.push(await bean.toPublicJSON());
monitorList.push(await bean.toPublicJSON(showTags));
}
return {

View File

@@ -7,10 +7,11 @@ dayjs.extend(timezone);
const axios = require("axios");
const { Prometheus } = require("../prometheus");
const { debug, UP, DOWN, PENDING, flipStatus, TimeLogger } = require("../../src/util");
const { tcping, ping, dnsResolve, checkCertificate, checkStatusCode, getTotalClientInRoom, setting } = require("../util-server");
const { tcping, ping, dnsResolve, checkCertificate, checkStatusCode, getTotalClientInRoom, setting, errorLog } = require("../util-server");
const { R } = require("redbean-node");
const { BeanModel } = require("redbean-node/dist/bean-model");
const { Notification } = require("../notification");
const { Proxy } = require("../proxy");
const { demoMode } = require("../config");
const version = require("../../package.json").version;
const apicache = require("../modules/apicache");
@@ -24,18 +25,22 @@ const apicache = require("../modules/apicache");
class Monitor extends BeanModel {
/**
* Return a object that ready to parse to JSON for public
* Return an object that ready to parse to JSON for public
* Only show necessary data to public
*/
async toPublicJSON() {
return {
async toPublicJSON(showTags = false) {
let obj = {
id: this.id,
name: this.name,
};
if (showTags) {
obj.tags = await this.getTags();
}
return obj;
}
/**
* Return a object that ready to parse to JSON
* Return an object that ready to parse to JSON
*/
async toJSON() {
@@ -49,7 +54,7 @@ class Monitor extends BeanModel {
notificationIDList[bean.notification_id] = true;
}
const tags = await R.getAll("SELECT mt.*, tag.name, tag.color FROM monitor_tag mt JOIN tag ON mt.tag_id = tag.id WHERE mt.monitor_id = ?", [this.id]);
const tags = await this.getTags();
return {
id: this.id,
@@ -58,6 +63,8 @@ class Monitor extends BeanModel {
method: this.method,
body: this.body,
headers: this.headers,
basic_auth_user: this.basic_auth_user,
basic_auth_pass: this.basic_auth_pass,
hostname: this.hostname,
port: this.port,
maxretries: this.maxretries,
@@ -75,11 +82,25 @@ class Monitor extends BeanModel {
dns_resolve_server: this.dns_resolve_server,
dns_last_result: this.dns_last_result,
pushToken: this.pushToken,
proxyId: this.proxy_id,
notificationIDList,
tags: tags,
};
}
async getTags() {
return await R.getAll("SELECT mt.*, tag.name, tag.color FROM monitor_tag mt JOIN tag ON mt.tag_id = tag.id WHERE mt.monitor_id = ?", [this.id]);
}
/**
* Encode user and password to Base64 encoding
* for HTTP "basic" auth, as per RFC-7617
* @returns {string}
*/
encodeBase64(user, pass) {
return Buffer.from(user + ":" + pass).toString("base64");
}
/**
* Parse to boolean
* @returns {boolean}
@@ -108,6 +129,19 @@ class Monitor extends BeanModel {
const beat = async () => {
let beatInterval = this.interval;
if (! beatInterval) {
beatInterval = 1;
}
if (demoMode) {
if (beatInterval < 20) {
console.log("beat interval too low, reset to 20s");
beatInterval = 20;
}
}
// Expose here for prometheus update
// undefined if not https
let tlsInfo = undefined;
@@ -141,25 +175,59 @@ class Monitor extends BeanModel {
// Do not do any queries/high loading things before the "bean.ping"
let startTime = dayjs().valueOf();
// HTTP basic auth
let basicAuthHeader = {};
if (this.basic_auth_user) {
basicAuthHeader = {
"Authorization": "Basic " + this.encodeBase64(this.basic_auth_user, this.basic_auth_pass),
};
}
const httpsAgentOptions = {
maxCachedSessions: 0, // Use Custom agent to disable session reuse (https://github.com/nodejs/node/issues/3940)
rejectUnauthorized: !this.getIgnoreTls(),
};
debug(`[${this.name}] Prepare Options for axios`);
const options = {
url: this.url,
method: (this.method || "get").toLowerCase(),
...(this.body ? { data: JSON.parse(this.body) } : {}),
timeout: this.interval * 1000 * 0.8,
headers: {
"Accept": "*/*",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
"User-Agent": "Uptime-Kuma/" + version,
...(this.headers ? JSON.parse(this.headers) : {}),
...(basicAuthHeader),
},
httpsAgent: new https.Agent({
maxCachedSessions: 0, // Use Custom agent to disable session reuse (https://github.com/nodejs/node/issues/3940)
rejectUnauthorized: ! this.getIgnoreTls(),
}),
maxRedirects: this.maxredirects,
validateStatus: (status) => {
return checkStatusCode(status, this.getAcceptedStatuscodes());
},
};
if (this.proxy_id) {
const proxy = await R.load("proxy", this.proxy_id);
if (proxy && proxy.active) {
const { httpAgent, httpsAgent } = Proxy.createAgents(proxy, {
httpsAgentOptions: httpsAgentOptions,
});
options.proxy = false;
options.httpAgent = httpAgent;
options.httpsAgent = httpsAgent;
}
}
if (!options.httpsAgent) {
options.httpsAgent = new https.Agent(httpsAgentOptions);
}
debug(`[${this.name}] Axios Options: ${JSON.stringify(options)}`);
debug(`[${this.name}] Axios Request`);
let res = await axios.request(options);
bean.msg = `${res.status} - ${res.statusText}`;
bean.ping = dayjs().valueOf() - startTime;
@@ -167,8 +235,16 @@ class Monitor extends BeanModel {
// Check certificate if https is used
let certInfoStartTime = dayjs().valueOf();
if (this.getUrl()?.protocol === "https:") {
debug(`[${this.name}] Check cert`);
try {
tlsInfo = await this.updateTlsInfo(checkCertificate(res));
let tlsInfoObject = checkCertificate(res);
tlsInfo = await this.updateTlsInfo(tlsInfoObject);
if (!this.getIgnoreTls()) {
debug(`[${this.name}] call sendCertNotification`);
await this.sendCertNotification(tlsInfoObject);
}
} catch (e) {
if (e.message !== "No TLS certificate in response") {
console.error(e.message);
@@ -264,11 +340,14 @@ class Monitor extends BeanModel {
debug("heartbeatCount" + heartbeatCount + " " + time);
if (heartbeatCount <= 0) {
// Fix #922, since previous heartbeat could be inserted by api, it should get from database
previousBeat = await Monitor.getPreviousHeartbeat(this.id);
throw new Error("No heartbeat in the time window");
} else {
// No need to insert successful heartbeat for push type, so end here
retries = 0;
this.heartbeatInterval = setTimeout(beat, this.interval * 1000);
this.heartbeatInterval = setTimeout(beat, beatInterval * 1000);
return;
}
@@ -342,15 +421,21 @@ class Monitor extends BeanModel {
}
}
let beatInterval = this.interval;
debug(`[${this.name}] Check isImportant`);
let isImportant = Monitor.isImportantBeat(isFirstBeat, previousBeat?.status, bean.status);
// Mark as important if status changed, ignore pending pings,
// Don't notify if disrupted changes to up
if (isImportant) {
bean.important = true;
debug(`[${this.name}] sendNotification`);
await Monitor.sendNotification(isFirstBeat, this, bean);
// Clear Status Page Cache
debug(`[${this.name}] apicache clear`);
apicache.clear();
} else {
bean.important = false;
}
@@ -366,41 +451,61 @@ class Monitor extends BeanModel {
console.warn(`Monitor #${this.id} '${this.name}': Failing: ${bean.msg} | Interval: ${beatInterval} seconds | Type: ${this.type}`);
}
debug(`[${this.name}] Send to socket`);
io.to(this.user_id).emit("heartbeat", bean.toJSON());
Monitor.sendStats(io, this.id, this.user_id);
debug(`[${this.name}] Store`);
await R.store(bean);
debug(`[${this.name}] prometheus.update`);
prometheus.update(bean, tlsInfo);
previousBeat = bean;
if (! this.isStop) {
if (demoMode) {
if (beatInterval < 20) {
console.log("beat interval too low, reset to 20s");
beatInterval = 20;
}
}
this.heartbeatInterval = setTimeout(beat, beatInterval * 1000);
debug(`[${this.name}] SetTimeout for next check.`);
this.heartbeatInterval = setTimeout(safeBeat, beatInterval * 1000);
} else {
console.log(`[${this.name}] isStop = true, no next check.`);
}
};
const safeBeat = async () => {
try {
await beat();
} catch (e) {
console.trace(e);
errorLog(e, false);
console.error("Please report to https://github.com/louislam/uptime-kuma/issues");
if (! this.isStop) {
console.log("Try to restart the monitor");
this.heartbeatInterval = setTimeout(safeBeat, this.interval * 1000);
}
}
};
// Delay Push Type
if (this.type === "push") {
setTimeout(() => {
beat();
safeBeat();
}, this.interval * 1000);
} else {
beat();
safeBeat();
}
}
stop() {
clearTimeout(this.heartbeatInterval);
this.isStop = true;
this.prometheus().remove();
}
prometheus() {
return new Prometheus(this);
}
/**
@@ -426,10 +531,36 @@ class Monitor extends BeanModel {
let tls_info_bean = await R.findOne("monitor_tls_info", "monitor_id = ?", [
this.id,
]);
if (tls_info_bean == null) {
tls_info_bean = R.dispense("monitor_tls_info");
tls_info_bean.monitor_id = this.id;
} else {
// Clear sent history if the cert changed.
try {
let oldCertInfo = JSON.parse(tls_info_bean.info_json);
let isValidObjects = oldCertInfo && oldCertInfo.certInfo && checkCertificateResult && checkCertificateResult.certInfo;
if (isValidObjects) {
if (oldCertInfo.certInfo.fingerprint256 !== checkCertificateResult.certInfo.fingerprint256) {
debug("Resetting sent_history");
await R.exec("DELETE FROM notification_sent_history WHERE type = 'certificate' AND monitor_id = ?", [
this.id
]);
} else {
debug("No need to reset sent_history");
debug(oldCertInfo.certInfo.fingerprint256);
debug(checkCertificateResult.certInfo.fingerprint256);
}
} else {
debug("Not valid object");
}
} catch (e) { }
}
tls_info_bean.info_json = JSON.stringify(checkCertificateResult);
await R.store(tls_info_bean);
@@ -577,9 +708,7 @@ class Monitor extends BeanModel {
static async sendNotification(isFirstBeat, monitor, bean) {
if (!isFirstBeat || bean.status === DOWN) {
let notificationList = await R.getAll("SELECT notification.* FROM notification, monitor_notification WHERE monitor_id = ? AND monitor_notification.notification_id = notification.id ", [
monitor.id,
]);
const notificationList = await Monitor.getNotificationList(monitor);
let text;
if (bean.status === UP) {
@@ -598,12 +727,82 @@ class Monitor extends BeanModel {
console.log(e);
}
}
// Clear Status Page Cache
apicache.clear();
}
}
static async getNotificationList(monitor) {
let notificationList = await R.getAll("SELECT notification.* FROM notification, monitor_notification WHERE monitor_id = ? AND monitor_notification.notification_id = notification.id ", [
monitor.id,
]);
return notificationList;
}
async sendCertNotification(tlsInfoObject) {
if (tlsInfoObject && tlsInfoObject.certInfo && tlsInfoObject.certInfo.daysRemaining) {
const notificationList = await Monitor.getNotificationList(this);
debug("call sendCertNotificationByTargetDays");
await this.sendCertNotificationByTargetDays(tlsInfoObject.certInfo.daysRemaining, 21, notificationList);
await this.sendCertNotificationByTargetDays(tlsInfoObject.certInfo.daysRemaining, 14, notificationList);
await this.sendCertNotificationByTargetDays(tlsInfoObject.certInfo.daysRemaining, 7, notificationList);
}
}
async sendCertNotificationByTargetDays(daysRemaining, targetDays, notificationList) {
if (daysRemaining > targetDays) {
debug(`No need to send cert notification. ${daysRemaining} > ${targetDays}`);
return;
}
if (notificationList.length > 0) {
let row = await R.getRow("SELECT * FROM notification_sent_history WHERE type = ? AND monitor_id = ? AND days = ?", [
"certificate",
this.id,
targetDays,
]);
// Sent already, no need to send again
if (row) {
debug("Sent already, no need to send again");
return;
}
let sent = false;
debug("Send certificate notification");
for (let notification of notificationList) {
try {
debug("Sending to " + notification.name);
await Notification.send(JSON.parse(notification.config), `[${this.name}][${this.url}] Certificate will be expired in ${daysRemaining} days`);
sent = true;
} catch (e) {
console.error("Cannot send cert notification to " + notification.name);
console.error(e);
}
}
if (sent) {
await R.exec("INSERT INTO notification_sent_history (type, monitor_id, days) VALUES(?, ?, ?)", [
"certificate",
this.id,
targetDays,
]);
}
} else {
debug("No notification, no need to send cert notification");
}
}
static async getPreviousHeartbeat(monitorID) {
return await R.getRow(`
SELECT status, time FROM heartbeat
WHERE id = (select MAX(id) from heartbeat where monitor_id = ?)
`, [
monitorID
]);
}
}
module.exports = Monitor;

21
server/model/proxy.js Normal file
View File

@@ -0,0 +1,21 @@
const { BeanModel } = require("redbean-node/dist/bean-model");
class Proxy extends BeanModel {
toJSON() {
return {
id: this._id,
userId: this._user_id,
protocol: this._protocol,
host: this._host,
port: this._port,
auth: !!this._auth,
username: this._username,
password: this._password,
active: !!this._active,
default: !!this._default,
createdDate: this._created_date,
};
}
}
module.exports = Proxy;

View File

@@ -0,0 +1,60 @@
const { BeanModel } = require("redbean-node/dist/bean-model");
const { R } = require("redbean-node");
class StatusPage extends BeanModel {
static async sendStatusPageList(io, socket) {
let result = {};
let list = await R.findAll("status_page", " ORDER BY title ");
for (let item of list) {
result[item.id] = await item.toJSON();
}
io.to(socket.userID).emit("statusPageList", result);
return list;
}
async toJSON() {
return {
id: this.id,
slug: this.slug,
title: this.title,
description: this.description,
icon: this.getIcon(),
theme: this.theme,
published: !!this.published,
showTags: !!this.show_tags,
};
}
async toPublicJSON() {
return {
slug: this.slug,
title: this.title,
description: this.description,
icon: this.getIcon(),
theme: this.theme,
published: !!this.published,
showTags: !!this.show_tags,
};
}
static async slugToID(slug) {
return await R.getCell("SELECT id FROM status_page WHERE slug = ? ", [
slug
]);
}
getIcon() {
if (!this.icon) {
return "/icon.svg";
} else {
return this.icon;
}
}
}
module.exports = StatusPage;

View File

@@ -0,0 +1,67 @@
const NotificationProvider = require("./notification-provider");
const { DOWN, UP } = require("../../src/util");
const axios = require("axios");
class Alerta extends NotificationProvider {
name = "alerta";
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
let okMsg = "Sent Successfully.";
try {
let alertaUrl = `${notification.alertaApiEndpoint}`;
let config = {
headers: {
"Content-Type": "application/json;charset=UTF-8",
"Authorization": "Key " + notification.alertaapiKey,
}
};
let data = {
environment: notification.alertaEnvironment,
severity: "critical",
correlate: [],
service: [ "UptimeKuma" ],
value: "Timeout",
tags: [ "uptimekuma" ],
attributes: {},
origin: "uptimekuma",
type: "exceptionAlert",
};
if (heartbeatJSON == null) {
let postData = Object.assign({
event: "msg",
text: msg,
group: "uptimekuma-msg",
resource: "Message",
}, data);
await axios.post(alertaUrl, postData, config);
} else {
let datadup = Object.assign( {
correlate: ["service_up", "service_down"],
event: monitorJSON["type"],
group: "uptimekuma-" + monitorJSON["type"],
resource: monitorJSON["name"],
}, data );
if (heartbeatJSON["status"] == DOWN) {
datadup.severity = notification.alertaAlertState; // critical
datadup.text = "Service " + monitorJSON["type"] + " is down.";
await axios.post(alertaUrl, datadup, config);
} else if (heartbeatJSON["status"] == UP) {
datadup.severity = notification.alertaRecoverState; // cleaned
datadup.text = "Service " + monitorJSON["type"] + " is up.";
await axios.post(alertaUrl, datadup, config);
}
}
return okMsg;
} catch (error) {
this.throwGeneralAxiosError(error);
}
}
}
module.exports = Alerta;

View File

@@ -0,0 +1,89 @@
//
// bark.js
// UptimeKuma
//
// Created by Lakr Aream on 2021/10/24.
// Copyright © 2021 Lakr Aream. All rights reserved.
//
const NotificationProvider = require("./notification-provider");
const { DOWN, UP } = require("../../src/util");
const { default: axios } = require("axios");
// bark is an APN bridge that sends notifications to Apple devices.
const barkNotificationGroup = "UptimeKuma";
const barkNotificationAvatar = "https://github.com/louislam/uptime-kuma/raw/master/public/icon.png";
const barkNotificationSound = "telegraph";
const successMessage = "Successes!";
class Bark extends NotificationProvider {
name = "Bark";
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
try {
var barkEndpoint = notification.barkEndpoint;
// check if the endpoint has a "/" suffix, if so, delete it first
if (barkEndpoint.endsWith("/")) {
barkEndpoint = barkEndpoint.substring(0, barkEndpoint.length - 1);
}
if (msg != null && heartbeatJSON != null && heartbeatJSON["status"] == UP) {
let title = "UptimeKuma Monitor Up";
return await this.postNotification(title, msg, barkEndpoint);
}
if (msg != null && heartbeatJSON != null && heartbeatJSON["status"] == DOWN) {
let title = "UptimeKuma Monitor Down";
return await this.postNotification(title, msg, barkEndpoint);
}
if (msg != null) {
let title = "UptimeKuma Message";
return await this.postNotification(title, msg, barkEndpoint);
}
} catch (error) {
throw error;
}
}
// add additional parameter for better on device styles (iOS 15 optimized)
appendAdditionalParameters(postUrl) {
// grouping all our notifications
postUrl += "?group=" + barkNotificationGroup;
// set icon to uptime kuma icon, 11kb should be fine
postUrl += "&icon=" + barkNotificationAvatar;
// picked a sound, this should follow system's mute status when arrival
postUrl += "&sound=" + barkNotificationSound;
return postUrl;
}
// thrown if failed to check result, result code should be in range 2xx
checkResult(result) {
if (result.status == null) {
throw new Error("Bark notification failed with invalid response!");
}
if (result.status < 200 || result.status >= 300) {
throw new Error("Bark notification failed with status code " + result.status);
}
}
async postNotification(title, subtitle, endpoint) {
// url encode title and subtitle
title = encodeURIComponent(title);
subtitle = encodeURIComponent(subtitle);
let postUrl = endpoint + "/" + title + "/" + subtitle;
postUrl = this.appendAdditionalParameters(postUrl);
let result = await axios.get(postUrl);
this.checkResult(result);
if (result.statusText != null) {
return "Bark notification succeed: " + result.statusText;
}
// because returned in range 200 ..< 300
return successMessage;
}
}
module.exports = Bark;

View File

@@ -0,0 +1,42 @@
const NotificationProvider = require("./notification-provider");
const axios = require("axios");
class ClickSendSMS extends NotificationProvider {
name = "clicksendsms";
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
let okMsg = "Sent Successfully.";
try {
console.log({ notification });
let config = {
headers: {
"Content-Type": "application/json",
"Authorization": "Basic " + Buffer.from(notification.clicksendsmsLogin + ":" + notification.clicksendsmsPassword).toString('base64'),
"Accept": "text/json",
}
};
let data = {
messages: [
{
"body": msg.replace(/[^\x00-\x7F]/g, ""),
"to": notification.clicksendsmsToNumber,
"source": "uptime-kuma",
"from": notification.clicksendsmsSenderName,
}
]
};
let resp = await axios.post("https://rest.clicksend.com/v3/sms/send", data, config);
if (resp.data.data.messages[0].status !== "SUCCESS") {
let error = "Something gone wrong. Api returned " + resp.data.data.messages[0].status + ".";
this.throwGeneralAxiosError(error);
}
return okMsg;
} catch (error) {
this.throwGeneralAxiosError(error);
}
}
}
module.exports = ClickSendSMS;

View File

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

View File

@@ -27,7 +27,7 @@ class Feishu extends NotificationProvider {
content: {
post: {
zh_cn: {
title: "UptimeKuma Alert: " + monitorJSON["name"],
title: "UptimeKuma Alert: [Down] " + monitorJSON["name"],
content: [
[
{
@@ -54,7 +54,7 @@ class Feishu extends NotificationProvider {
content: {
post: {
zh_cn: {
title: "UptimeKuma Alert: " + monitorJSON["name"],
title: "UptimeKuma Alert: [Up] " + monitorJSON["name"],
content: [
[
{

View File

@@ -0,0 +1,47 @@
const NotificationProvider = require("./notification-provider");
const axios = require("axios");
const { setting } = require("../util-server");
const { getMonitorRelativeURL } = require("../../src/util");
const { DOWN, UP } = require("../../src/util");
class GoogleChat extends NotificationProvider {
name = "GoogleChat";
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
let okMsg = "Sent Successfully.";
try {
// Google Chat message formatting: https://developers.google.com/chat/api/guides/message-formats/basic
let textMsg = ''
if (heartbeatJSON && heartbeatJSON.status === UP) {
textMsg = `✅ Application is back online\n`;
} else if (heartbeatJSON && heartbeatJSON.status === DOWN) {
textMsg = `🔴 Application went down\n`;
}
if (monitorJSON && monitorJSON.name) {
textMsg += `*${monitorJSON.name}*\n`;
}
textMsg += `${msg}`;
const baseURL = await setting("primaryBaseURL");
if (baseURL && monitorJSON) {
textMsg += `\n${baseURL + getMonitorRelativeURL(monitorJSON.id)}`;
}
const data = {
"text": textMsg,
};
await axios.post(notification.googleChatWebhookURL, data);
return okMsg;
} catch (error) {
this.throwGeneralAxiosError(error);
}
}
}
module.exports = GoogleChat;

View File

@@ -0,0 +1,42 @@
const NotificationProvider = require("./notification-provider");
const axios = require("axios");
class Gorush extends NotificationProvider {
name = "gorush";
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
let okMsg = "Sent Successfully.";
let platformMapping = {
"ios": 1,
"android": 2,
"huawei": 3,
};
try {
let data = {
"notifications": [
{
"tokens": [notification.gorushDeviceToken],
"platform": platformMapping[notification.gorushPlatform],
"message": msg,
// Optional
"title": notification.gorushTitle,
"priority": notification.gorushPriority,
"retry": parseInt(notification.gorushRetry) || 0,
"topic": notification.gorushTopic,
}
]
};
let config = {};
await axios.post(`${notification.gorushServerURL}/api/push`, data, config);
return okMsg;
} catch (error) {
this.throwGeneralAxiosError(error);
}
}
}
module.exports = Gorush;

View File

@@ -20,7 +20,7 @@ class Mattermost extends NotificationProvider {
return okMsg;
}
const mattermostChannel = notification.mattermostchannel;
const mattermostChannel = notification.mattermostchannel.toLowerCase();
const mattermostIconEmoji = notification.mattermosticonemo;
const mattermostIconUrl = notification.mattermosticonurl;

View File

@@ -7,40 +7,35 @@ class Pushover extends NotificationProvider {
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
let okMsg = "Sent Successfully.";
let pushoverlink = "https://api.pushover.net/1/messages.json"
let pushoverlink = "https://api.pushover.net/1/messages.json";
let data = {
"message": "<b>Uptime Kuma Alert</b>\n\n<b>Message</b>:" + msg,
"user": notification.pushoveruserkey,
"token": notification.pushoverapptoken,
"sound": notification.pushoversounds,
"priority": notification.pushoverpriority,
"title": notification.pushovertitle,
"retry": "30",
"expire": "3600",
"html": 1,
};
if (notification.pushoverdevice) {
data.device = notification.pushoverdevice;
}
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)
await axios.post(pushoverlink, data);
return okMsg;
} else {
data.message += "\n<b>Time (UTC)</b>:" + heartbeatJSON["time"];
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)
this.throwGeneralAxiosError(error);
}
}

View File

@@ -0,0 +1,44 @@
const NotificationProvider = require("./notification-provider");
const axios = require("axios");
class SerwerSMS extends NotificationProvider {
name = "serwersms";
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
let okMsg = "Sent Successfully.";
try {
let config = {
headers: {
"Content-Type": "application/json",
}
};
let data = {
"username": notification.serwersmsUsername,
"password": notification.serwersmsPassword,
"phone": notification.serwersmsPhoneNumber,
"text": msg.replace(/[^\x00-\x7F]/g, ""),
"sender": notification.serwersmsSenderName,
};
let resp = await axios.post("https://api2.serwersms.pl/messages/send_sms", data, config);
if (!resp.data.success) {
if (resp.data.error) {
let error = `SerwerSMS.pl API returned error code ${resp.data.error.code} (${resp.data.error.type}) with error message: ${resp.data.error.message}`;
this.throwGeneralAxiosError(error);
} else {
let error = "SerwerSMS.pl API returned an unexpected response";
this.throwGeneralAxiosError(error);
}
}
return okMsg;
} catch (error) {
this.throwGeneralAxiosError(error);
}
}
}
module.exports = SerwerSMS;

View File

@@ -12,8 +12,23 @@ class SMTP extends NotificationProvider {
host: notification.smtpHost,
port: notification.smtpPort,
secure: notification.smtpSecure,
tls: {
rejectUnauthorized: notification.smtpIgnoreTLSError || false,
}
};
// Fix #1129
if (notification.smtpDkimDomain) {
config.dkim = {
domainName: notification.smtpDkimDomain,
keySelector: notification.smtpDkimKeySelector,
privateKey: notification.smtpDkimPrivateKey,
hashAlgo: notification.smtpDkimHashAlgo,
headerFieldNames: notification.smtpDkimheaderFieldNames,
skipFields: notification.smtpDkimskipFields,
};
}
// Should fix the issue in https://github.com/louislam/uptime-kuma/issues/26#issuecomment-896373904
if (notification.smtpUsername || notification.smtpPassword) {
config.auth = {
@@ -87,9 +102,6 @@ class SMTP extends NotificationProvider {
to: notification.smtpTo,
subject: subject,
text: bodyTextContent,
tls: {
rejectUnauthorized: notification.smtpIgnoreTLSError || false,
},
});
return "Sent Successfully.";

View File

@@ -0,0 +1,41 @@
const NotificationProvider = require("./notification-provider");
const axios = require("axios");
const { setting } = require("../util-server");
const { getMonitorRelativeURL } = require("../../src/util");
class Stackfield extends NotificationProvider {
name = "stackfield";
async send(notification, msg, monitorJSON = null) {
let okMsg = "Sent Successfully.";
try {
// Stackfield message formatting: https://www.stackfield.com/help/formatting-messages-2001
let textMsg = "+Uptime Kuma Alert+";
if (monitorJSON && monitorJSON.name) {
textMsg += `\n*${monitorJSON.name}*`;
}
textMsg += `\n${msg}`;
const baseURL = await setting("primaryBaseURL");
if (baseURL) {
textMsg += `\n${baseURL + getMonitorRelativeURL(monitorJSON.id)}`;
}
const data = {
"Title": textMsg,
};
await axios.post(notification.stackfieldwebhookURL, data);
return okMsg;
} catch (error) {
this.throwGeneralAxiosError(error);
}
}
}
module.exports = Stackfield;

View File

@@ -0,0 +1,23 @@
const NotificationProvider = require("./notification-provider");
const axios = require("axios");
class TechulusPush extends NotificationProvider {
name = "PushByTechulus";
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
let okMsg = "Sent Successfully.";
try {
await axios.post(`https://push.techulus.com/api/v1/notify/${notification.pushAPIKey}`, {
"title": "Uptime-Kuma",
"body": msg,
})
return okMsg;
} catch (error) {
this.throwGeneralAxiosError(error)
}
}
}
module.exports = TechulusPush;

View File

@@ -0,0 +1,47 @@
const NotificationProvider = require("./notification-provider");
const axios = require("axios");
const { DOWN, UP } = require("../../src/util");
class WeCom extends NotificationProvider {
name = "WeCom";
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
let okMsg = "Sent Successfully.";
try {
let WeComUrl = "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=" + notification.weComBotKey;
let config = {
headers: {
"Content-Type": "application/json"
}
};
let body = this.composeMessage(heartbeatJSON, msg);
await axios.post(WeComUrl, body, config);
return okMsg;
} catch (error) {
this.throwGeneralAxiosError(error);
}
}
composeMessage(heartbeatJSON, msg) {
let title;
if (msg != null && heartbeatJSON != null && heartbeatJSON['status'] == UP) {
title = "UptimeKuma Monitor Up";
}
if (msg != null && heartbeatJSON != null && heartbeatJSON["status"] == DOWN) {
title = "UptimeKuma Monitor Down";
}
if (msg != null) {
title = "UptimeKuma Message";
}
return {
msgtype: "text",
text: {
content: title + msg
}
};
}
}
module.exports = WeCom;

View File

@@ -8,9 +8,11 @@ const Mattermost = require("./notification-providers/mattermost");
const Matrix = require("./notification-providers/matrix");
const Octopush = require("./notification-providers/octopush");
const PromoSMS = require("./notification-providers/promosms");
const ClickSendSMS = require("./notification-providers/clicksendsms");
const Pushbullet = require("./notification-providers/pushbullet");
const Pushover = require("./notification-providers/pushover");
const Pushy = require("./notification-providers/pushy");
const TechulusPush = require("./notification-providers/techulus-push");
const RocketChat = require("./notification-providers/rocket-chat");
const Signal = require("./notification-providers/signal");
const Slack = require("./notification-providers/slack");
@@ -21,6 +23,13 @@ const Webhook = require("./notification-providers/webhook");
const Feishu = require("./notification-providers/feishu");
const AliyunSms = require("./notification-providers/aliyun-sms");
const DingDing = require("./notification-providers/dingding");
const Bark = require("./notification-providers/bark");
const SerwerSMS = require("./notification-providers/serwersms");
const Stackfield = require("./notification-providers/stackfield");
const WeCom = require("./notification-providers/wecom");
const GoogleChat = require("./notification-providers/google-chat");
const Gorush = require("./notification-providers/gorush");
const Alerta = require("./notification-providers/alerta");
class Notification {
@@ -45,15 +54,24 @@ class Notification {
new Matrix(),
new Octopush(),
new PromoSMS(),
new ClickSendSMS(),
new Pushbullet(),
new Pushover(),
new Pushy(),
new TechulusPush(),
new RocketChat(),
new Signal(),
new Slack(),
new SMTP(),
new Telegram(),
new Webhook(),
new Bark(),
new SerwerSMS(),
new Stackfield(),
new WeCom(),
new GoogleChat(),
new Gorush(),
new Alerta(),
];
for (let item of list) {

View File

@@ -48,7 +48,7 @@ function Ping(host, options) {
this._args = (options.args) ? options.args : [ "-n", "-t", timeout, "-c", "1", host ];
this._regmatch = /=([0-9.]+?) ms/;
} else if (util.FBSD) {
} else if (util.BSD) {
this._bin = "/sbin/ping";
const defaultArgs = [ "-n", "-t", timeout, "-c", "1", host ];

View File

@@ -60,7 +60,9 @@ class Prometheus {
}
try {
monitor_cert_days_remaining.set(this.monitorLabelValues, tlsInfo.certInfo.daysRemaining);
if (tlsInfo.certInfo != null) {
monitor_cert_days_remaining.set(this.monitorLabelValues, tlsInfo.certInfo.daysRemaining);
}
} catch (e) {
console.error(e);
}
@@ -84,6 +86,16 @@ class Prometheus {
}
}
remove() {
try {
monitor_cert_days_remaining.remove(this.monitorLabelValues);
monitor_cert_is_valid.remove(this.monitorLabelValues);
monitor_response_time.remove(this.monitorLabelValues);
monitor_status.remove(this.monitorLabelValues);
} catch (e) {
console.error(e);
}
}
}
module.exports = {

170
server/proxy.js Normal file
View File

@@ -0,0 +1,170 @@
const { R } = require("redbean-node");
const HttpProxyAgent = require("http-proxy-agent");
const HttpsProxyAgent = require("https-proxy-agent");
const SocksProxyAgent = require("socks-proxy-agent");
const { debug } = require("../src/util");
class Proxy {
static SUPPORTED_PROXY_PROTOCOLS = ["http", "https", "socks", "socks5", "socks4"]
/**
* Saves and updates given proxy entity
*
* @param proxy
* @param proxyID
* @param userID
* @return {Promise<Bean>}
*/
static async save(proxy, proxyID, userID) {
let bean;
if (proxyID) {
bean = await R.findOne("proxy", " id = ? AND user_id = ? ", [proxyID, userID]);
if (!bean) {
throw new Error("proxy not found");
}
} else {
bean = R.dispense("proxy");
}
// Make sure given proxy protocol is supported
if (!this.SUPPORTED_PROXY_PROTOCOLS.includes(proxy.protocol)) {
throw new Error(`
Unsupported proxy protocol "${proxy.protocol}.
Supported protocols are ${this.SUPPORTED_PROXY_PROTOCOLS.join(", ")}."`
);
}
// When proxy is default update deactivate old default proxy
if (proxy.default) {
await R.exec("UPDATE proxy SET `default` = 0 WHERE `default` = 1");
}
bean.user_id = userID;
bean.protocol = proxy.protocol;
bean.host = proxy.host;
bean.port = proxy.port;
bean.auth = proxy.auth;
bean.username = proxy.username;
bean.password = proxy.password;
bean.active = proxy.active || true;
bean.default = proxy.default || false;
await R.store(bean);
if (proxy.applyExisting) {
await applyProxyEveryMonitor(bean.id, userID);
}
return bean;
}
/**
* Deletes proxy with given id and removes it from monitors
*
* @param proxyID
* @param userID
* @return {Promise<void>}
*/
static async delete(proxyID, userID) {
const bean = await R.findOne("proxy", " id = ? AND user_id = ? ", [proxyID, userID]);
if (!bean) {
throw new Error("proxy not found");
}
// Delete removed proxy from monitors if exists
await R.exec("UPDATE monitor SET proxy_id = null WHERE proxy_id = ?", [proxyID]);
// Delete proxy from list
await R.trash(bean);
}
/**
* Create HTTP and HTTPS agents related with given proxy bean object
*
* @param proxy proxy bean object
* @param options http and https agent options
* @return {{httpAgent: Agent, httpsAgent: Agent}}
*/
static createAgents(proxy, options) {
const { httpAgentOptions, httpsAgentOptions } = options || {};
let agent;
let httpAgent;
let httpsAgent;
const proxyOptions = {
protocol: proxy.protocol,
host: proxy.host,
port: proxy.port,
};
if (proxy.auth) {
proxyOptions.auth = `${proxy.username}:${proxy.password}`;
}
debug(`Proxy Options: ${JSON.stringify(proxyOptions)}`);
debug(`HTTP Agent Options: ${JSON.stringify(httpAgentOptions)}`);
debug(`HTTPS Agent Options: ${JSON.stringify(httpsAgentOptions)}`);
switch (proxy.protocol) {
case "http":
case "https":
httpAgent = new HttpProxyAgent({
...httpAgentOptions || {},
...proxyOptions
});
httpsAgent = new HttpsProxyAgent({
...httpsAgentOptions || {},
...proxyOptions,
});
break;
case "socks":
case "socks5":
case "socks4":
agent = new SocksProxyAgent({
...httpAgentOptions,
...httpsAgentOptions,
...proxyOptions,
});
httpAgent = agent;
httpsAgent = agent;
break;
default: throw new Error(`Unsupported proxy protocol provided. ${proxy.protocol}`);
}
return {
httpAgent,
httpsAgent
};
}
}
/**
* Applies given proxy id to monitors
*
* @param proxyID
* @param userID
* @return {Promise<void>}
*/
async function applyProxyEveryMonitor(proxyID, userID) {
// Find all monitors with id and proxy id
const monitors = await R.getAll("SELECT id, proxy_id FROM monitor WHERE user_id = ?", [userID]);
// Update proxy id not match with given proxy id
for (const monitor of monitors) {
if (monitor.proxy_id !== proxyID) {
await R.exec("UPDATE monitor SET proxy_id = ? WHERE id = ?", [proxyID, monitor.id]);
}
}
}
module.exports = {
Proxy,
};

47
server/rate-limiter.js Normal file
View File

@@ -0,0 +1,47 @@
const { RateLimiter } = require("limiter");
const { debug } = require("../src/util");
class KumaRateLimiter {
constructor(config) {
this.errorMessage = config.errorMessage;
this.rateLimiter = new RateLimiter(config);
}
async pass(callback, num = 1) {
const remainingRequests = await this.removeTokens(num);
debug("Rate Limit (remainingRequests):" + remainingRequests);
if (remainingRequests < 0) {
if (callback) {
callback({
ok: false,
msg: this.errorMessage,
});
}
return false;
}
return true;
}
async removeTokens(num = 1) {
return await this.rateLimiter.removeTokens(num);
}
}
const loginRateLimiter = new KumaRateLimiter({
tokensPerInterval: 20,
interval: "minute",
fireImmediately: true,
errorMessage: "Too frequently, try again later."
});
const twoFaRateLimiter = new KumaRateLimiter({
tokensPerInterval: 30,
interval: "minute",
fireImmediately: true,
errorMessage: "Too frequently, try again later."
});
module.exports = {
loginRateLimiter,
twoFaRateLimiter,
};

View File

@@ -6,6 +6,7 @@ const apicache = require("../modules/apicache");
const Monitor = require("../model/monitor");
const dayjs = require("dayjs");
const { UP, flipStatus, debug } = require("../../src/util");
const StatusPage = require("../model/status_page");
let router = express.Router();
let cache = apicache.middleware;
@@ -31,12 +32,7 @@ router.get("/api/push/:pushToken", async (request, response) => {
throw new Error("Monitor not found or not active.");
}
const previousHeartbeat = await R.getRow(`
SELECT status, time FROM heartbeat
WHERE id = (select MAX(id) from heartbeat where monitor_id = ?)
`, [
monitor.id
]);
const previousHeartbeat = await Monitor.getPreviousHeartbeat(monitor.id);
let status = UP;
if (monitor.isUpsideDown()) {
@@ -87,88 +83,80 @@ router.get("/api/push/:pushToken", async (request, response) => {
}
});
// Status Page Config
router.get("/api/status-page/config", async (_request, response) => {
// Status page config, incident, monitor list
router.get("/api/status-page/:slug", cache("5 minutes"), async (request, response) => {
allowDevAllOrigin(response);
let slug = request.params.slug;
let config = await getSettings("statusPage");
// Get Status Page
let statusPage = await R.findOne("status_page", " slug = ? ", [
slug
]);
if (! config.statusPageTheme) {
config.statusPageTheme = "light";
if (!statusPage) {
response.statusCode = 404;
response.json({
msg: "Not Found"
});
return;
}
if (! config.statusPagePublished) {
config.statusPagePublished = true;
}
if (! config.title) {
config.title = "Uptime Kuma";
}
response.json(config);
});
// Status Page - Get the current Incident
// Can fetch only if published
router.get("/api/status-page/incident", async (_, response) => {
allowDevAllOrigin(response);
try {
await checkPublished();
let incident = await R.findOne("incident", " pin = 1 AND active = 1");
// Incident
let incident = await R.findOne("incident", " pin = 1 AND active = 1 AND status_page_id = ? ", [
statusPage.id,
]);
if (incident) {
incident = incident.toPublicJSON();
}
// Public Group List
const publicGroupList = [];
const showTags = !!statusPage.show_tags;
debug("Show Tags???" + showTags);
const list = await R.find("group", " public = 1 AND status_page_id = ? ORDER BY weight ", [
statusPage.id
]);
for (let groupBean of list) {
let monitorGroup = await groupBean.toPublicJSON(showTags);
publicGroupList.push(monitorGroup);
}
// Response
response.json({
ok: true,
config: await statusPage.toPublicJSON(),
incident,
publicGroupList
});
} catch (error) {
send403(response, error.message);
}
});
// Status Page - Monitor List
// Can fetch only if published
router.get("/api/status-page/monitor-list", cache("5 minutes"), async (_request, response) => {
allowDevAllOrigin(response);
try {
await checkPublished();
const publicGroupList = [];
let list = await R.find("group", " public = 1 ORDER BY weight ");
for (let groupBean of list) {
publicGroupList.push(await groupBean.toPublicJSON());
}
response.json(publicGroupList);
} catch (error) {
send403(response, error.message);
}
});
// Status Page Polling Data
// Can fetch only if published
router.get("/api/status-page/heartbeat", cache("5 minutes"), async (_request, response) => {
router.get("/api/status-page/heartbeat/:slug", cache("1 minutes"), async (request, response) => {
allowDevAllOrigin(response);
try {
await checkPublished();
let heartbeatList = {};
let uptimeList = {};
let slug = request.params.slug;
let statusPageID = await StatusPage.slugToID(slug);
let monitorIDList = await R.getCol(`
SELECT monitor_group.monitor_id FROM monitor_group, \`group\`
WHERE monitor_group.group_id = \`group\`.id
AND public = 1
`);
AND \`group\`.status_page_id = ?
`, [
statusPageID
]);
for (let monitorID of monitorIDList) {
let list = await R.getAll(`
@@ -197,22 +185,12 @@ router.get("/api/status-page/heartbeat", cache("5 minutes"), async (_request, re
}
});
async function checkPublished() {
if (! await isPublished()) {
throw new Error("The status page is not published");
}
}
/**
* Default is published
* @returns {Promise<boolean>}
*/
async function isPublished() {
const value = await setting("statusPagePublished");
if (value === null) {
return true;
}
return value;
return true;
}
function send403(res, msg = "") {

View File

@@ -1,4 +1,15 @@
console.log("Welcome to Uptime Kuma");
// Check Node.js Version
const nodeVersion = parseInt(process.versions.node.split(".")[0]);
const requiredVersion = 14;
console.log(`Your Node.js version: ${nodeVersion}`);
if (nodeVersion < requiredVersion) {
console.error(`Error: Your Node.js version is not supported, please upgrade to Node.js >= ${requiredVersion}.`);
process.exit(-1);
}
const args = require("args-parser")(process.argv);
const { sleep, debug, getRandomInt, genSecret } = require("../src/util");
const config = require("./config");
@@ -31,6 +42,7 @@ debug("Importing prometheus-api-metrics");
const prometheusAPIMetrics = require("prometheus-api-metrics");
debug("Importing compare-versions");
const compareVersions = require("compare-versions");
const { passwordStrength } = require("check-password-strength");
debug("Importing 2FA Modules");
const notp = require("notp");
@@ -40,17 +52,21 @@ console.log("Importing this project modules");
debug("Importing Monitor");
const Monitor = require("./model/monitor");
debug("Importing Settings");
const { getSettings, setSettings, setting, initJWTSecret, checkLogin, startUnitTest, FBSD } = require("./util-server");
const { getSettings, setSettings, setting, initJWTSecret, checkLogin, startUnitTest, FBSD, errorLog, doubleCheckPassword } = require("./util-server");
debug("Importing Notification");
const { Notification } = require("./notification");
Notification.init();
debug("Importing Proxy");
const { Proxy } = require("./proxy");
debug("Importing Database");
const Database = require("./database");
debug("Importing Background Jobs");
const { initBackgroundJobs } = require("./jobs");
const { loginRateLimiter, twoFaRateLimiter } = require("./rate-limiter");
const { basicAuth } = require("./auth");
const { login } = require("./auth");
@@ -77,6 +93,8 @@ const port = parseInt(process.env.UPTIME_KUMA_PORT || process.env.PORT || args.p
// SSL
const sslKey = process.env.UPTIME_KUMA_SSL_KEY || process.env.SSL_KEY || args["ssl-key"] || undefined;
const sslCert = process.env.UPTIME_KUMA_SSL_CERT || process.env.SSL_CERT || args["ssl-cert"] || undefined;
const disableFrameSameOrigin = !!process.env.UPTIME_KUMA_DISABLE_FRAME_SAMEORIGIN || args["disable-frame-sameorigin"] || false;
const cloudflaredToken = args["cloudflared-token"] || process.env.UPTIME_KUMA_CLOUDFLARED_TOKEN || undefined;
// 2FA / notp verification defaults
const twofa_verification_opts = {
@@ -114,11 +132,24 @@ const io = new Server(server);
module.exports.io = io;
// Must be after io instantiation
const { sendNotificationList, sendHeartbeatList, sendImportantHeartbeatList, sendInfo } = require("./client");
const { sendNotificationList, sendHeartbeatList, sendImportantHeartbeatList, sendInfo, sendProxyList } = require("./client");
const { statusPageSocketHandler } = require("./socket-handlers/status-page-socket-handler");
const databaseSocketHandler = require("./socket-handlers/database-socket-handler");
const TwoFA = require("./2fa");
const StatusPage = require("./model/status_page");
const { cloudflaredSocketHandler, autoStart: cloudflaredAutoStart } = require("./socket-handlers/cloudflared-socket-handler");
app.use(express.json());
// Global Middleware
app.use(function (req, res, next) {
if (!disableFrameSameOrigin) {
res.setHeader("X-Frame-Options", "SAMEORIGIN");
}
res.removeHeader("X-Powered-By");
next();
});
/**
* Total WebSocket client connected to server currently, no actual use
* @type {number}
@@ -147,13 +178,23 @@ let needSetup = false;
* Cache Index HTML
* @type {string}
*/
let indexHTML = fs.readFileSync("./dist/index.html").toString();
let indexHTML = "";
try {
indexHTML = fs.readFileSync("./dist/index.html").toString();
} catch (e) {
// "dist/index.html" is not necessary for development
if (process.env.NODE_ENV !== "development") {
console.error("Error: Cannot find 'dist/index.html', did you install correctly?");
process.exit(1);
}
}
exports.entryPage = "dashboard";
(async () => {
Database.init(args);
await initDatabase();
await initDatabase(testMode);
exports.entryPage = await setting("entryPage");
@@ -163,6 +204,15 @@ exports.entryPage = "dashboard";
// Normal Router here
// ***************************
// Entry Page
app.get("/", async (_request, response) => {
if (exports.entryPage && exports.entryPage.startsWith("statusPage-")) {
response.redirect("/status/" + exports.entryPage.replace("statusPage-", ""));
} else {
response.redirect("/dashboard");
}
});
// Robots.txt
app.get("/robots.txt", async (_request, response) => {
let txt = "User-agent: *\nDisallow:";
@@ -192,7 +242,7 @@ exports.entryPage = "dashboard";
const apiRouter = require("./routers/api-router");
app.use(apiRouter);
// Universal Route Handler, must be at the end of all express route.
// Universal Route Handler, must be at the end of all express routes.
app.get("*", async (_request, response) => {
if (_request.originalUrl.startsWith("/upload/")) {
response.status(404).send("File not found.");
@@ -260,10 +310,24 @@ exports.entryPage = "dashboard";
socket.on("login", async (data, callback) => {
console.log("Login");
// Checking
if (typeof callback !== "function") {
return;
}
if (!data) {
return;
}
// Login Rate Limit
if (! await loginRateLimiter.pass(callback)) {
return;
}
let user = await login(data.username, data.password);
if (user) {
if (user.twofaStatus == 0) {
if (user.twofa_status == 0) {
afterLogin(socket, user);
callback({
ok: true,
@@ -273,7 +337,7 @@ exports.entryPage = "dashboard";
});
}
if (user.twofaStatus == 1 && !data.token) {
if (user.twofa_status == 1 && !data.token) {
callback({
tokenRequired: true,
});
@@ -282,8 +346,14 @@ exports.entryPage = "dashboard";
if (data.token) {
let verify = notp.totp.verify(data.token, user.twofa_secret, twofa_verification_opts);
if (verify && verify.delta == 0) {
if (user.twofa_last_token !== data.token && verify) {
afterLogin(socket, user);
await R.exec("UPDATE `user` SET twofa_last_token = ? WHERE id = ? ", [
data.token,
socket.userID,
]);
callback({
ok: true,
token: jwt.sign({
@@ -307,21 +377,34 @@ exports.entryPage = "dashboard";
});
socket.on("logout", async (callback) => {
// Rate Limit
if (! await loginRateLimiter.pass(callback)) {
return;
}
socket.leave(socket.userID);
socket.userID = null;
callback();
if (typeof callback === "function") {
callback();
}
});
socket.on("prepare2FA", async (callback) => {
socket.on("prepare2FA", async (currentPassword, callback) => {
try {
if (! await twoFaRateLimiter.pass(callback)) {
return;
}
checkLogin(socket);
await doubleCheckPassword(socket, currentPassword);
let user = await R.findOne("user", " id = ? AND active = 1 ", [
socket.userID,
]);
if (user.twofa_status == 0) {
let newSecret = await genSecret();
let newSecret = genSecret();
let encodedSecret = base32.encode(newSecret);
// Google authenticator doesn't like equal signs
@@ -349,14 +432,19 @@ exports.entryPage = "dashboard";
} catch (error) {
callback({
ok: false,
msg: "Error while trying to prepare 2FA.",
msg: error.message,
});
}
});
socket.on("save2FA", async (callback) => {
socket.on("save2FA", async (currentPassword, callback) => {
try {
if (! await twoFaRateLimiter.pass(callback)) {
return;
}
checkLogin(socket);
await doubleCheckPassword(socket, currentPassword);
await R.exec("UPDATE `user` SET twofa_status = 1 WHERE id = ? ", [
socket.userID,
@@ -369,18 +457,20 @@ exports.entryPage = "dashboard";
} catch (error) {
callback({
ok: false,
msg: "Error while trying to change 2FA.",
msg: error.message,
});
}
});
socket.on("disable2FA", async (callback) => {
socket.on("disable2FA", async (currentPassword, callback) => {
try {
checkLogin(socket);
if (! await twoFaRateLimiter.pass(callback)) {
return;
}
await R.exec("UPDATE `user` SET twofa_status = 0 WHERE id = ? ", [
socket.userID,
]);
checkLogin(socket);
await doubleCheckPassword(socket, currentPassword);
await TwoFA.disable2FA(socket.userID);
callback({
ok: true,
@@ -389,36 +479,47 @@ exports.entryPage = "dashboard";
} catch (error) {
callback({
ok: false,
msg: "Error while trying to change 2FA.",
msg: error.message,
});
}
});
socket.on("verifyToken", async (token, callback) => {
let user = await R.findOne("user", " id = ? AND active = 1 ", [
socket.userID,
]);
socket.on("verifyToken", async (token, currentPassword, callback) => {
try {
checkLogin(socket);
await doubleCheckPassword(socket, currentPassword);
let verify = notp.totp.verify(token, user.twofa_secret, twofa_verification_opts);
let user = await R.findOne("user", " id = ? AND active = 1 ", [
socket.userID,
]);
if (verify && verify.delta == 0) {
callback({
ok: true,
valid: true,
});
} else {
let verify = notp.totp.verify(token, user.twofa_secret, twofa_verification_opts);
if (user.twofa_last_token !== token && verify) {
callback({
ok: true,
valid: true,
});
} else {
callback({
ok: false,
msg: "Invalid Token.",
valid: false,
});
}
} catch (error) {
callback({
ok: false,
msg: "Invalid Token.",
valid: false,
msg: error.message,
});
}
});
socket.on("twoFAStatus", async (callback) => {
checkLogin(socket);
try {
checkLogin(socket);
let user = await R.findOne("user", " id = ? AND active = 1 ", [
socket.userID,
]);
@@ -435,9 +536,10 @@ exports.entryPage = "dashboard";
});
}
} catch (error) {
console.log(error);
callback({
ok: false,
msg: "Error while trying to get 2FA status.",
msg: error.message,
});
}
});
@@ -448,8 +550,12 @@ exports.entryPage = "dashboard";
socket.on("setup", async (username, password, callback) => {
try {
if (passwordStrength(password).value === "Too weak") {
throw new Error("Password is too weak. It should contain alphabetic and numeric characters. It must be at least 6 characters in length.");
}
if ((await R.count("user")) !== 0) {
throw new Error("Uptime Kuma has been setup. If you want to setup again, please delete the database.");
throw new Error("Uptime Kuma has been initialized. If you want to run setup again, please delete the database.");
}
let user = R.dispense("user");
@@ -494,8 +600,8 @@ exports.entryPage = "dashboard";
await updateMonitorNotification(bean.id, notificationIDList);
await startMonitor(socket.userID, bean.id);
await sendMonitorList(socket);
await startMonitor(socket.userID, bean.id);
callback({
ok: true,
@@ -522,12 +628,17 @@ exports.entryPage = "dashboard";
throw new Error("Permission denied.");
}
// Reset Prometheus labels
monitorList[monitor.id]?.prometheus()?.remove();
bean.name = monitor.name;
bean.type = monitor.type;
bean.url = monitor.url;
bean.method = monitor.method;
bean.body = monitor.body;
bean.headers = monitor.headers;
bean.basic_auth_user = monitor.basic_auth_user;
bean.basic_auth_pass = monitor.basic_auth_pass;
bean.interval = monitor.interval;
bean.retryInterval = monitor.retryInterval;
bean.hostname = monitor.hostname;
@@ -541,6 +652,7 @@ exports.entryPage = "dashboard";
bean.dns_resolve_type = monitor.dns_resolve_type;
bean.dns_resolve_server = monitor.dns_resolve_server;
bean.pushToken = monitor.pushToken;
bean.proxyId = Number.isInteger(monitor.proxyId) ? monitor.proxyId : null;
await R.store(bean);
@@ -607,6 +719,38 @@ exports.entryPage = "dashboard";
}
});
socket.on("getMonitorBeats", async (monitorID, period, callback) => {
try {
checkLogin(socket);
console.log(`Get Monitor Beats: ${monitorID} User ID: ${socket.userID}`);
if (period == null) {
throw new Error("Invalid period.");
}
let list = await R.getAll(`
SELECT * FROM heartbeat
WHERE monitor_id = ? AND
time > DATETIME('now', '-' || ? || ' hours')
ORDER BY time ASC
`, [
monitorID,
period,
]);
callback({
ok: true,
data: list,
});
} catch (e) {
callback({
ok: false,
msg: e.message,
});
}
});
// Start or Resume the monitor
socket.on("resumeMonitor", async (monitorID, callback) => {
try {
@@ -837,26 +981,22 @@ exports.entryPage = "dashboard";
try {
checkLogin(socket);
if (! password.currentPassword) {
if (! password.newPassword) {
throw new Error("Invalid new password");
}
let user = await R.findOne("user", " id = ? AND active = 1 ", [
socket.userID,
]);
if (user && passwordHash.verify(password.currentPassword, user.password)) {
user.resetPassword(password.newPassword);
callback({
ok: true,
msg: "Password has been updated successfully.",
});
} else {
throw new Error("Incorrect current password");
if (passwordStrength(password.newPassword).value === "Too weak") {
throw new Error("Password is too weak. It should contain alphabetic and numeric characters. It must be at least 6 characters in length.");
}
let user = await doubleCheckPassword(socket, password.currentPassword);
await user.resetPassword(password.newPassword);
callback({
ok: true,
msg: "Password has been updated successfully.",
});
} catch (e) {
callback({
ok: false,
@@ -882,10 +1022,14 @@ exports.entryPage = "dashboard";
}
});
socket.on("setSettings", async (data, callback) => {
socket.on("setSettings", async (data, currentPassword, callback) => {
try {
checkLogin(socket);
if (data.disableAuth) {
await doubleCheckPassword(socket, currentPassword);
}
await setSettings("general", data);
exports.entryPage = data.entryPage;
@@ -967,6 +1111,52 @@ exports.entryPage = "dashboard";
}
});
socket.on("addProxy", async (proxy, proxyID, callback) => {
try {
checkLogin(socket);
const proxyBean = await Proxy.save(proxy, proxyID, socket.userID);
await sendProxyList(socket);
if (proxy.applyExisting) {
await restartMonitors(socket.userID);
}
callback({
ok: true,
msg: "Saved",
id: proxyBean.id,
});
} catch (e) {
callback({
ok: false,
msg: e.message,
});
}
});
socket.on("deleteProxy", async (proxyID, callback) => {
try {
checkLogin(socket);
await Proxy.delete(proxyID, socket.userID);
await sendProxyList(socket);
await restartMonitors(socket.userID);
callback({
ok: true,
msg: "Deleted",
});
} catch (e) {
callback({
ok: false,
msg: e.message,
});
}
});
socket.on("checkApprise", async (callback) => {
try {
checkLogin(socket);
@@ -985,6 +1175,7 @@ exports.entryPage = "dashboard";
console.log(`Importing Backup, User ID: ${socket.userID}, Version: ${backupData.version}`);
let notificationListData = backupData.notificationList;
let proxyListData = backupData.proxyList;
let monitorListData = backupData.monitorList;
let version17x = compareVersions.compare(backupData.version, "1.7.0", ">=");
@@ -1003,6 +1194,7 @@ exports.entryPage = "dashboard";
await R.exec("DELETE FROM monitor_tag");
await R.exec("DELETE FROM tag");
await R.exec("DELETE FROM monitor");
await R.exec("DELETE FROM proxy");
}
// Only starts importing if the backup file contains at least one notification
@@ -1022,6 +1214,24 @@ exports.entryPage = "dashboard";
}
}
// Only starts importing if the backup file contains at least one proxy
if (proxyListData.length >= 1) {
const proxies = await R.findAll("proxy");
// Loop over proxy list and save proxies
for (const proxy of proxyListData) {
const exists = proxies.find(item => item.id === proxy.id);
// Do not process when proxy already exists in import handle is skip and keep
if (["skip", "keep"].includes(importHandle) && !exists) {
return;
}
// Save proxy as new entry if exists update exists one
await Proxy.save(proxy, exists ? proxy.id : undefined, proxy.userId);
}
}
// Only starts importing if the backup file contains at least one monitor
if (monitorListData.length >= 1) {
// Get every existing monitor name and puts them in one simple string
@@ -1056,6 +1266,8 @@ exports.entryPage = "dashboard";
method: monitorListData[i].method || "GET",
body: monitorListData[i].body,
headers: monitorListData[i].headers,
basic_auth_user: monitorListData[i].basic_auth_user,
basic_auth_pass: monitorListData[i].basic_auth_pass,
interval: monitorListData[i].interval,
retryInterval: retryInterval,
hostname: monitorListData[i].hostname,
@@ -1069,6 +1281,7 @@ exports.entryPage = "dashboard";
dns_resolve_type: monitorListData[i].dns_resolve_type,
dns_resolve_server: monitorListData[i].dns_resolve_server,
notificationIDList: {},
proxy_id: monitorListData[i].proxy_id || null,
};
if (monitorListData[i].pushToken) {
@@ -1222,6 +1435,8 @@ exports.entryPage = "dashboard";
// Status Page Socket Handler for admin only
statusPageSocketHandler(socket);
cloudflaredSocketHandler(socket);
databaseSocketHandler(socket);
debug("added all socket handlers");
@@ -1263,6 +1478,9 @@ exports.entryPage = "dashboard";
initBackgroundJobs(args);
// Start cloudflared at the end if configured
await cloudflaredAutoStart(cloudflaredToken);
})();
async function updateMonitorNotification(monitorID, notificationIDList) {
@@ -1303,9 +1521,12 @@ async function afterLogin(socket, user) {
let monitorList = await sendMonitorList(socket);
sendNotificationList(socket);
sendProxyList(socket);
await sleep(500);
await StatusPage.sendStatusPageList(io, socket);
for (let monitorID in monitorList) {
await sendHeartbeatList(socket, monitorID);
}
@@ -1333,14 +1554,14 @@ async function getMonitorJSONList(userID) {
return result;
}
async function initDatabase() {
async function initDatabase(testMode = false) {
if (! fs.existsSync(Database.path)) {
console.log("Copying Database");
fs.copyFileSync(Database.templatePath, Database.path);
}
console.log("Connecting to Database");
await Database.connect();
console.log("Connecting to the Database");
await Database.connect(testMode);
console.log("Connected");
// Patch the database
@@ -1393,6 +1614,19 @@ async function restartMonitor(userID, monitorID) {
return await startMonitor(userID, monitorID);
}
async function restartMonitors(userID) {
// Fetch all active monitors for user
const monitors = await R.getAll("SELECT id FROM monitor WHERE active = 1 AND user_id = ?", [userID]);
for (const monitor of monitors) {
// Start updated monitor
await startMonitor(userID, monitor.id);
// Give some delays, so all monitors won't make request at the same moment when just start the server.
await sleep(getRandomInt(300, 1000));
}
}
async function pauseMonitor(userID, monitorID) {
await checkOwner(userID, monitorID);
@@ -1439,7 +1673,7 @@ async function shutdownFunction(signal) {
}
function finalFunction() {
console.log("Graceful shutdown successfully!");
console.log("Graceful shutdown successful!");
}
gracefulShutdown(server, {
@@ -1454,5 +1688,6 @@ gracefulShutdown(server, {
// Catch unexpected errors here
process.addListener("unhandledRejection", (error, promise) => {
console.trace(error);
errorLog(error, false);
console.error("If you keep encountering errors, please report to https://github.com/louislam/uptime-kuma/issues");
});

View File

@@ -0,0 +1,85 @@
const { checkLogin, setSetting, setting, doubleCheckPassword } = require("../util-server");
const { CloudflaredTunnel } = require("node-cloudflared-tunnel");
const { io } = require("../server");
const prefix = "cloudflared_";
const cloudflared = new CloudflaredTunnel();
cloudflared.change = (running, message) => {
io.to("cloudflared").emit(prefix + "running", running);
io.to("cloudflared").emit(prefix + "message", message);
};
cloudflared.error = (errorMessage) => {
io.to("cloudflared").emit(prefix + "errorMessage", errorMessage);
};
module.exports.cloudflaredSocketHandler = (socket) => {
socket.on(prefix + "join", async () => {
try {
checkLogin(socket);
socket.join("cloudflared");
io.to(socket.userID).emit(prefix + "installed", cloudflared.checkInstalled());
io.to(socket.userID).emit(prefix + "running", cloudflared.running);
io.to(socket.userID).emit(prefix + "token", await setting("cloudflaredTunnelToken"));
} catch (error) { }
});
socket.on(prefix + "leave", async () => {
try {
checkLogin(socket);
socket.leave("cloudflared");
} catch (error) { }
});
socket.on(prefix + "start", async (token) => {
try {
checkLogin(socket);
if (token && typeof token === "string") {
await setSetting("cloudflaredTunnelToken", token);
cloudflared.token = token;
} else {
cloudflared.token = null;
}
cloudflared.start();
} catch (error) { }
});
socket.on(prefix + "stop", async (currentPassword, callback) => {
try {
checkLogin(socket);
await doubleCheckPassword(socket, currentPassword);
cloudflared.stop();
} catch (error) {
callback({
ok: false,
msg: error.message,
});
}
});
socket.on(prefix + "removeToken", async () => {
try {
checkLogin(socket);
await setSetting("cloudflaredTunnelToken", "");
} catch (error) { }
});
};
module.exports.autoStart = async (token) => {
if (!token) {
token = await setting("cloudflaredTunnelToken");
} else {
// Override the current token via args or env var
await setSetting("cloudflaredTunnelToken", token);
console.log("Use cloudflared token from args or env var");
}
if (token) {
console.log("Start cloudflared");
cloudflared.token = token;
cloudflared.start();
}
};

View File

@@ -0,0 +1,37 @@
const { checkLogin } = require("../util-server");
const Database = require("../database");
module.exports = (socket) => {
// Post or edit incident
socket.on("getDatabaseSize", async (callback) => {
try {
checkLogin(socket);
callback({
ok: true,
size: Database.getSize(),
});
} catch (error) {
callback({
ok: false,
msg: error.message,
});
}
});
socket.on("shrinkDatabase", async (callback) => {
try {
checkLogin(socket);
Database.shrink();
callback({
ok: true,
});
} catch (error) {
callback({
ok: false,
msg: error.message,
});
}
});
};

View File

@@ -1,25 +1,36 @@
const { R } = require("redbean-node");
const { checkLogin, setSettings } = require("../util-server");
const { checkLogin, setSettings, setSetting } = require("../util-server");
const dayjs = require("dayjs");
const { debug } = require("../../src/util");
const ImageDataURI = require("../image-data-uri");
const Database = require("../database");
const apicache = require("../modules/apicache");
const StatusPage = require("../model/status_page");
const server = require("../server");
module.exports.statusPageSocketHandler = (socket) => {
// Post or edit incident
socket.on("postIncident", async (incident, callback) => {
socket.on("postIncident", async (slug, incident, callback) => {
try {
checkLogin(socket);
await R.exec("UPDATE incident SET pin = 0 ");
let statusPageID = await StatusPage.slugToID(slug);
if (!statusPageID) {
throw new Error("slug is not found");
}
await R.exec("UPDATE incident SET pin = 0 WHERE status_page_id = ? ", [
statusPageID
]);
let incidentBean;
if (incident.id) {
incidentBean = await R.findOne("incident", " id = ?", [
incident.id
incidentBean = await R.findOne("incident", " id = ? AND status_page_id = ? ", [
incident.id,
statusPageID
]);
}
@@ -31,6 +42,7 @@ module.exports.statusPageSocketHandler = (socket) => {
incidentBean.content = incident.content;
incidentBean.style = incident.style;
incidentBean.pin = true;
incidentBean.status_page_id = statusPageID;
if (incident.id) {
incidentBean.lastUpdatedDate = R.isoDateTime(dayjs.utc());
@@ -52,11 +64,15 @@ module.exports.statusPageSocketHandler = (socket) => {
}
});
socket.on("unpinIncident", async (callback) => {
socket.on("unpinIncident", async (slug, callback) => {
try {
checkLogin(socket);
await R.exec("UPDATE incident SET pin = 0 WHERE pin = 1");
let statusPageID = await StatusPage.slugToID(slug);
await R.exec("UPDATE incident SET pin = 0 WHERE pin = 1 AND status_page_id = ? ", [
statusPageID
]);
callback({
ok: true,
@@ -71,13 +87,23 @@ module.exports.statusPageSocketHandler = (socket) => {
// Save Status Page
// imgDataUrl Only Accept PNG!
socket.on("saveStatusPage", async (config, imgDataUrl, publicGroupList, callback) => {
socket.on("saveStatusPage", async (slug, config, imgDataUrl, publicGroupList, callback) => {
try {
checkLogin(socket);
checkSlug(config.slug);
checkLogin(socket);
apicache.clear();
// Save Config
let statusPage = await R.findOne("status_page", " slug = ? ", [
slug
]);
if (!statusPage) {
throw new Error("No slug?");
}
const header = "data:image/png;base64,";
// Check logo format
@@ -88,16 +114,28 @@ module.exports.statusPageSocketHandler = (socket) => {
throw new Error("Only allowed PNG logo.");
}
const filename = `logo${statusPage.id}.png`;
// Convert to file
await ImageDataURI.outputFile(imgDataUrl, Database.uploadDir + "logo.png");
config.logo = "/upload/logo.png?t=" + Date.now();
await ImageDataURI.outputFile(imgDataUrl, Database.uploadDir + filename);
config.logo = `/upload/${filename}?t=` + Date.now();
} else {
config.icon = imgDataUrl;
}
// Save Config
await setSettings("statusPage", config);
statusPage.slug = config.slug;
statusPage.title = config.title;
statusPage.description = config.description;
statusPage.icon = config.logo;
statusPage.theme = config.theme;
//statusPage.published = ;
//statusPage.search_engine_index = ;
statusPage.show_tags = config.showTags;
//statusPage.password = null;
statusPage.modified_date = R.isoDateTime();
await R.store(statusPage);
// Save Public Group List
const groupIDList = [];
@@ -106,13 +144,15 @@ module.exports.statusPageSocketHandler = (socket) => {
for (let group of publicGroupList) {
let groupBean;
if (group.id) {
groupBean = await R.findOne("group", " id = ? AND public = 1 ", [
group.id
groupBean = await R.findOne("group", " id = ? AND public = 1 AND status_page_id = ? ", [
group.id,
statusPage.id
]);
} else {
groupBean = R.dispense("group");
}
groupBean.status_page_id = statusPage.id;
groupBean.name = group.name;
groupBean.public = true;
groupBean.weight = groupOrder++;
@@ -124,7 +164,6 @@ module.exports.statusPageSocketHandler = (socket) => {
]);
let monitorOrder = 1;
console.log(group.monitorList);
for (let monitor of group.monitorList) {
let relationBean = R.dispense("monitor_group");
@@ -141,7 +180,18 @@ module.exports.statusPageSocketHandler = (socket) => {
// Delete groups that not in the list
debug("Delete groups that not in the list");
const slots = groupIDList.map(() => "?").join(",");
await R.exec(`DELETE FROM \`group\` WHERE id NOT IN (${slots})`, groupIDList);
const data = [
...groupIDList,
statusPage.id
];
await R.exec(`DELETE FROM \`group\` WHERE id NOT IN (${slots}) AND status_page_id = ?`, data);
// Also change entry page to new slug if it is the default one, and slug is changed.
if (server.entryPage === "statusPage-" + slug && statusPage.slug !== slug) {
server.entryPage = "statusPage-" + statusPage.slug;
await setSetting("entryPage", server.entryPage, "general");
}
callback({
ok: true,
@@ -149,7 +199,7 @@ module.exports.statusPageSocketHandler = (socket) => {
});
} catch (error) {
console.log(error);
console.error(error);
callback({
ok: false,
@@ -158,4 +208,115 @@ module.exports.statusPageSocketHandler = (socket) => {
}
});
// Add a new status page
socket.on("addStatusPage", async (title, slug, callback) => {
try {
checkLogin(socket);
title = title?.trim();
slug = slug?.trim();
// Check empty
if (!title || !slug) {
throw new Error("Please input all fields");
}
// Make sure slug is string
if (typeof slug !== "string") {
throw new Error("Slug -Accept string only");
}
// lower case only
slug = slug.toLowerCase();
checkSlug(slug);
let statusPage = R.dispense("status_page");
statusPage.slug = slug;
statusPage.title = title;
statusPage.theme = "light";
statusPage.icon = "";
await R.store(statusPage);
callback({
ok: true,
msg: "OK!"
});
} catch (error) {
console.error(error);
callback({
ok: false,
msg: error.message,
});
}
});
// Delete a status page
socket.on("deleteStatusPage", async (slug, callback) => {
try {
checkLogin(socket);
let statusPageID = await StatusPage.slugToID(slug);
if (statusPageID) {
// Reset entry page if it is the default one.
if (server.entryPage === "statusPage-" + slug) {
server.entryPage = "dashboard";
await setSetting("entryPage", server.entryPage, "general");
}
// No need to delete records from `status_page_cname`, because it has cascade foreign key.
// But for incident & group, it is hard to add cascade foreign key during migration, so they have to be deleted manually.
// Delete incident
await R.exec("DELETE FROM incident WHERE status_page_id = ? ", [
statusPageID
]);
// Delete group
await R.exec("DELETE FROM `group` WHERE status_page_id = ? ", [
statusPageID
]);
// Delete status_page
await R.exec("DELETE FROM status_page WHERE id = ? ", [
statusPageID
]);
} else {
throw new Error("Status Page is not found");
}
callback({
ok: true,
});
} catch (error) {
callback({
ok: false,
msg: error.message,
});
}
});
};
/**
* Check slug a-z, 0-9, - only
* Regex from: https://stackoverflow.com/questions/22454258/js-regex-string-validation-for-slug
*/
function checkSlug(slug) {
if (typeof slug !== "string") {
throw new Error("Slug must be string");
}
slug = slug.trim();
if (!slug) {
throw new Error("Slug cannot be empty");
}
if (!slug.match(/^[A-Za-z0-9]+(?:-[A-Za-z0-9]+)*$/)) {
throw new Error("Invalid Slug");
}
}

View File

@@ -1,19 +1,21 @@
const tcpp = require("tcp-ping");
const Ping = require("./ping-lite");
const { R } = require("redbean-node");
const { debug } = require("../src/util");
const { debug, genSecret } = require("../src/util");
const passwordHash = require("./password-hash");
const dayjs = require("dayjs");
const { Resolver } = require("dns");
const child_process = require("child_process");
const iconv = require("iconv-lite");
const chardet = require("chardet");
const fs = require("fs");
const nodeJsUtil = require("util");
// From ping-lite
exports.WIN = /^win/.test(process.platform);
exports.LIN = /^linux/.test(process.platform);
exports.MAC = /^darwin/.test(process.platform);
exports.FBSD = /^freebsd/.test(process.platform);
exports.BSD = /bsd$/.test(process.platform);
/**
* Init or reset JWT secret
@@ -29,7 +31,7 @@ exports.initJWTSecret = async () => {
jwtSecretBean.key = "jwtSecret";
}
jwtSecretBean.value = passwordHash.generate(dayjs() + "");
jwtSecretBean.value = passwordHash.generate(genSecret());
await R.store(jwtSecretBean);
return jwtSecretBean;
};
@@ -199,8 +201,13 @@ const getDaysRemaining = (validFrom, validTo) => {
// param: info - the chain obtained from getPeerCertificate()
const parseCertificateInfo = function (info) {
let link = info;
let i = 0;
const existingList = {};
while (link) {
debug(`[${i}] ${link.fingerprint}`);
if (!link.valid_from || !link.valid_to) {
break;
}
@@ -208,15 +215,24 @@ const parseCertificateInfo = function (info) {
link.validFor = link.subjectaltname?.replace(/DNS:|IP Address:/g, "").split(", ");
link.daysRemaining = getDaysRemaining(new Date(), link.validTo);
existingList[link.fingerprint] = true;
// Move up the chain until loop is encountered
if (link.issuerCertificate == null) {
break;
} else if (link.fingerprint == link.issuerCertificate.fingerprint) {
} else if (link.issuerCertificate.fingerprint in existingList) {
debug(`[Last] ${link.issuerCertificate.fingerprint}`);
link.issuerCertificate = null;
break;
} else {
link = link.issuerCertificate;
}
// Should be no use, but just in case.
if (i > 500) {
throw new Error("Dead loop occurred in parseCertificateInfo");
}
i++;
}
return info;
@@ -226,6 +242,7 @@ exports.checkCertificate = function (res) {
const info = res.request.res.socket.getPeerCertificate(true);
const valid = res.request.res.socket.authorized || false;
debug("Parsing Certificate Info");
const parsedInfo = parseCertificateInfo(info);
return {
@@ -303,6 +320,28 @@ exports.checkLogin = (socket) => {
}
};
/**
* For logged-in users, double-check the password
* @param socket
* @param currentPassword
* @returns {Promise<Bean>}
*/
exports.doubleCheckPassword = async (socket, currentPassword) => {
if (typeof currentPassword !== "string") {
throw new Error("Wrong data type?");
}
let user = await R.findOne("user", " id = ? AND active = 1 ", [
socket.userID,
]);
if (!user || !passwordHash.verify(currentPassword, user.password)) {
throw new Error("Incorrect current password");
}
return user;
};
exports.startUnitTest = async () => {
console.log("Starting unit test...");
const npm = /^win/.test(process.platform) ? "npm.cmd" : "npm";
@@ -332,3 +371,24 @@ exports.convertToUTF8 = (body) => {
const str = iconv.decode(body, guessEncoding);
return str.toString();
};
let logFile;
try {
logFile = fs.createWriteStream("./data/error.log", {
flags: "a"
});
} catch (_) { }
exports.errorLog = (error, outputToConsole = true) => {
try {
if (logFile) {
const dateTime = R.isoDateTime();
logFile.write(`[${dateTime}] ` + nodeJsUtil.format(error) + "\n");
if (outputToConsole) {
console.error(error);
}
}
} catch (_) { }
};

View File

@@ -92,6 +92,10 @@ textarea.form-control {
}
}
.btn-dark {
background-color: #161B22;
}
@media (max-width: 550px) {
.table-shadow-box {
padding: 10px !important;
@@ -144,6 +148,10 @@ textarea.form-control {
background-color: #090c10;
color: $dark-font-color;
mark, .mark {
background-color: #b6ad86;
}
&::-webkit-scrollbar-thumb, ::-webkit-scrollbar-thumb {
background: $dark-border-color;
}
@@ -156,13 +164,24 @@ textarea.form-control {
.form-check-input {
background-color: $dark-bg2;
border-color: $dark-border-color;
}
.input-group-text {
background-color: #282f39;
border-color: $dark-border-color;
color: $dark-font-color;
}
.form-check-input:checked {
border-color: $primary; // Re-apply bootstrap border
}
.form-switch .form-check-input {
background-color: #232f3b;
}
a,
a:not(.btn),
.table,
.nav-link {
color: $dark-font-color;
@@ -189,7 +208,7 @@ textarea.form-control {
opacity: 1;
}
.table-hover > tbody > tr:hover {
.table-hover > tbody > tr:hover > * {
--bs-table-accent-bg: #070a10;
color: $dark-font-color;
}
@@ -313,13 +332,24 @@ textarea.form-control {
opacity: 0;
}
.slide-fade-up-enter-active {
transition: all 0.2s $easing-in;
}
.slide-fade-up-leave-active {
transition: all 0.2s $easing-in;
}
.slide-fade-up-enter-from,
.slide-fade-up-leave-to {
transform: translateY(-50px);
opacity: 0;
}
.monitor-list {
&.scrollbar {
min-height: calc(100vh - 240px);
max-height: calc(100vh - 30px);
overflow-y: auto;
position: sticky;
top: 10px;
height: calc(100% - 65px);
}
.item {
@@ -346,6 +376,10 @@ textarea.form-control {
&.active {
background-color: #cdf8f4;
}
.tags {
// Removes margin to line up tags list with uptime percentage
margin-left: -0.25rem;
}
}
}
@@ -415,6 +449,10 @@ textarea.form-control {
border-radius: 10px !important;
}
.spinner {
color: $primary;
}
// Localization
@import "localization.scss";

View File

@@ -12,6 +12,7 @@ $dark-font-color2: #020b05;
$dark-bg: #0d1117;
$dark-bg2: #070a10;
$dark-border-color: #1d2634;
$dark-header-bg: #161b22;
$easing-in: cubic-bezier(0.54, 0.78, 0.55, 0.97);
$easing-out: cubic-bezier(0.25, 0.46, 0.45, 0.94);

View File

@@ -167,7 +167,7 @@ export default {
},
getBeatTitle(beat) {
return `${this.$root.datetime(beat.time)} - ${beat.msg}`;
return `${this.$root.datetime(beat.time)}` + ((beat.msg) ? ` - ${beat.msg}` : ``);
}
},
}

View File

@@ -16,8 +16,8 @@
<div v-if="tokenRequired">
<div class="form-floating mt-3">
<input id="floatingToken" v-model="token" type="text" maxlength="6" class="form-control" placeholder="123456">
<label for="floatingToken">{{ $t("Token") }}</label>
<input id="otp" v-model="token" type="text" maxlength="6" class="form-control" placeholder="123456">
<label for="otp">{{ $t("Token") }}</label>
</div>
</div>

View File

@@ -1,5 +1,5 @@
<template>
<div class="shadow-box mb-3">
<div class="shadow-box mb-3" :style="boxStyle">
<div class="list-header">
<div class="placeholder"></div>
<div class="search-wrapper">
@@ -9,7 +9,9 @@
<a v-if="searchText != ''" class="search-icon" @click="clearSearchText">
<font-awesome-icon icon="times" />
</a>
<input v-model="searchText" class="form-control search-input" :placeholder="$t('Search...')" />
<form>
<input v-model="searchText" class="form-control search-input" :placeholder="$t('Search...')" autocomplete="off" />
</form>
</div>
</div>
<div class="monitor-list" :class="{ scrollbar: scrollbar }">
@@ -63,9 +65,16 @@ export default {
data() {
return {
searchText: "",
windowTop: 0,
};
},
computed: {
boxStyle() {
return {
height: `calc(100vh - 160px + ${this.windowTop}px)`,
};
},
sortedMonitorList() {
let result = Object.values(this.$root.monitorList);
@@ -108,7 +117,20 @@ export default {
return result;
},
},
mounted() {
window.addEventListener("scroll", this.onScroll);
},
beforeUnmount() {
window.removeEventListener("scroll", this.onScroll);
},
methods: {
onScroll() {
if (window.top.scrollY <= 133) {
this.windowTop = window.top.scrollY;
} else {
this.windowTop = 133;
}
},
monitorURL(id) {
return getMonitorRelativeURL(id);
},
@@ -122,6 +144,12 @@ export default {
<style lang="scss" scoped>
@import "../assets/vars.scss";
.shadow-box {
height: calc(100vh - 150px);
position: sticky;
top: 10px;
}
.small-padding {
padding-left: 5px !important;
padding-right: 5px !important;
@@ -137,11 +165,17 @@ export default {
justify-content: space-between;
.dark & {
background-color: #161b22;
background-color: $dark-header-bg;
border-bottom: 0;
}
}
.dark {
.footer {
// background-color: $dark-bg;
}
}
@media (max-width: 770px) {
.list-header {
margin: -20px;

View File

@@ -85,7 +85,9 @@ export default {
model: null,
processing: false,
id: null,
notificationTypes: Object.keys(NotificationFormList),
notificationTypes: Object.keys(NotificationFormList).sort((a, b) => {
return a.toLowerCase().localeCompare(b.toLowerCase());
}),
notification: {
name: "",
/** @type { null | keyof NotificationFormList } */
@@ -143,12 +145,9 @@ export default {
this.id = null;
this.notification = {
name: "",
type: null,
type: "telegram",
isDefault: false,
};
// Set Default value here
this.notification.type = this.notificationTypes[0];
}
this.modal.show();

View File

@@ -1,16 +1,34 @@
<template>
<LineChart :chart-data="chartData" :options="chartOptions" />
<div>
<div class="period-options">
<button type="button" class="btn btn-light dropdown-toggle btn-period-toggle" data-bs-toggle="dropdown" aria-expanded="false">
{{ chartPeriodOptions[chartPeriodHrs] }}&nbsp;
</button>
<ul class="dropdown-menu dropdown-menu-end">
<li v-for="(item, key) in chartPeriodOptions" :key="key">
<a class="dropdown-item" :class="{ active: chartPeriodHrs == key }" href="#" @click="chartPeriodHrs = key">{{ item }}</a>
</li>
</ul>
</div>
<div class="chart-wrapper" :class="{ loading : loading}">
<LineChart :chart-data="chartData" :options="chartOptions" />
</div>
</div>
</template>
<script>
<script lang="ts">
import { BarController, BarElement, Chart, Filler, LinearScale, LineController, LineElement, PointElement, TimeScale, Tooltip } from "chart.js";
import dayjs from "dayjs";
import utc from "dayjs/plugin/utc";
import timezone from "dayjs/plugin/timezone";
import "chartjs-adapter-dayjs";
import { LineChart } from "vue-chart-3";
import { useToast } from "vue-toastification";
import { UP, DOWN, PENDING } from "../util.ts";
dayjs.extend(utc);
dayjs.extend(timezone);
const toast = useToast();
Chart.register(LineController, BarController, LineElement, PointElement, TimeScale, BarElement, LinearScale, Tooltip, Filler);
@@ -24,8 +42,23 @@ export default {
},
data() {
return {
loading: false,
// Configurable filtering on top of the returned data
chartPeriodHrs: 6,
chartPeriodHrs: 0,
chartPeriodOptions: {
0: this.$t("recent"),
3: "3h",
6: "6h",
24: "24h",
168: "1w",
},
// A heartbeatList for 3h, 6h, 24h, 1w
// Uses the $root.heartbeatList when value is null
heartbeatList: null
};
},
computed: {
@@ -117,7 +150,7 @@ export default {
},
callbacks: {
label: (context) => {
return ` ${new Intl.NumberFormat().format(context.parsed.y)} ms`
return ` ${new Intl.NumberFormat().format(context.parsed.y)} ms`;
},
}
},
@@ -125,27 +158,36 @@ export default {
display: false,
},
},
}
};
},
chartData() {
let pingData = []; // Ping Data for Line Chart, y-axis contains ping time
let downData = []; // Down Data for Bar Chart, y-axis is 1 if target is down, 0 if target is up
if (this.monitorId in this.$root.heartbeatList) {
this.$root.heartbeatList[this.monitorId]
.filter(
(beat) => dayjs.utc(beat.time).tz(this.$root.timezone).isAfter(dayjs().subtract(this.chartPeriodHrs, "hours")))
.map((beat) => {
const x = this.$root.datetime(beat.time);
pingData.push({
x,
y: beat.ping,
});
downData.push({
x,
y: beat.status === 0 ? 1 : 0,
})
let heartbeatList = this.heartbeatList ||
(this.monitorId in this.$root.heartbeatList && this.$root.heartbeatList[this.monitorId]) ||
[];
heartbeatList
.filter(
// Filtering as data gets appended
// not the most efficient, but works for now
(beat) => dayjs.utc(beat.time).tz(this.$root.timezone).isAfter(
dayjs().subtract(Math.max(this.chartPeriodHrs, 6), "hours")
)
)
.map((beat) => {
const x = this.$root.datetime(beat.time);
pingData.push({
x,
y: beat.ping,
});
}
downData.push({
x,
y: beat.status === DOWN ? 1 : 0,
});
});
return {
datasets: [
{
@@ -172,5 +214,110 @@ export default {
};
},
},
watch: {
// Update chart data when the selected chart period changes
chartPeriodHrs: function (newPeriod) {
if (newPeriod == "0") {
newPeriod = null;
this.heartbeatList = null;
} else {
this.loading = true;
this.$root.getMonitorBeats(this.monitorId, newPeriod, (res) => {
if (!res.ok) {
toast.error(res.msg);
} else {
this.heartbeatList = res.data;
}
this.loading = false;
});
}
}
},
created() {
// Setup Watcher on the root heartbeatList,
// And mirror latest change to this.heartbeatList
this.$watch(() => this.$root.heartbeatList[this.monitorId],
(heartbeatList) => {
if (this.chartPeriodHrs != 0) {
const newBeat = heartbeatList.at(-1);
if (newBeat && dayjs.utc(newBeat.time) > dayjs.utc(this.heartbeatList.at(-1)?.time)) {
this.heartbeatList.push(heartbeatList.at(-1));
}
}
},
{ deep: true }
);
}
};
</script>
<style lang="scss" scoped>
@import "../assets/vars.scss";
.form-select {
width: unset;
display: inline-flex;
}
.period-options {
padding: 0.1em 1em;
margin-bottom: -1.2em;
float: right;
position: relative;
z-index: 10;
.dropdown-menu {
padding: 0;
min-width: 50px;
font-size: 0.9em;
.dark & {
background: $dark-bg;
}
.dropdown-item {
border-radius: 0.3rem;
padding: 2px 16px 4px 16px;
.dark & {
background: $dark-bg;
}
.dark &:hover {
background: $dark-font-color;
}
}
.dark & .dropdown-item.active {
background: $primary;
color: $dark-font-color2;
}
}
.btn-period-toggle {
padding: 2px 15px;
background: transparent;
border: 0;
color: $link-color;
opacity: 0.7;
font-size: 0.9em;
&::after {
vertical-align: 0.155em;
}
.dark & {
color: $dark-font-color;
}
}
}
.chart-wrapper {
margin-bottom: 0.5em;
&.loading {
filter: blur(10px);
}
}
</style>

View File

@@ -0,0 +1,206 @@
<template>
<form @submit.prevent="submit">
<div ref="modal" class="modal fade" tabindex="-1" data-bs-backdrop="static">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 id="exampleModalLabel" class="modal-title">
{{ $t("Setup Proxy") }}
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close" />
</div>
<div class="modal-body">
<div class="mb-3">
<label for="proxy-protocol" class="form-label">{{ $t("Proxy Protocol") }}</label>
<select id="proxy-protocol" v-model="proxy.protocol" class="form-select">
<option value="https">HTTPS</option>
<option value="http">HTTP</option>
<option value="socks">SOCKS</option>
<option value="socks5">SOCKS v5</option>
<option value="socks4">SOCKS v4</option>
</select>
</div>
<div class="mb-3">
<label for="proxy-host" class="form-label">{{ $t("Proxy Server") }}</label>
<div class="d-flex">
<input id="proxy-host" v-model="proxy.host" type="text" class="form-control" required :placeholder="$t('Server Address')">
<input v-model="proxy.port" type="number" class="form-control ms-2" style="width: 100px" required min="1" max="65535" :placeholder="$t('Port')">
</div>
</div>
<div class="mb-3">
<div class="form-check form-switch">
<input id="mark-auth" v-model="proxy.auth" class="form-check-input" type="checkbox">
<label for="mark-auth" class="form-check-label">{{ $t("Proxy server has authentication") }}</label>
</div>
</div>
<div v-if="proxy.auth" class="mb-3">
<label for="proxy-username" class="form-label">{{ $t("User") }}</label>
<input id="proxy-username" v-model="proxy.username" type="text" class="form-control" required>
</div>
<div v-if="proxy.auth" class="mb-3">
<label for="proxy-password" class="form-label">{{ $t("Password") }}</label>
<input id="proxy-password" v-model="proxy.password" type="password" class="form-control" required>
</div>
<div class="mb-3 mt-4">
<hr class="dropdown-divider mb-4">
<div class="form-check form-switch">
<input id="mark-active" v-model="proxy.active" class="form-check-input" type="checkbox">
<label for="mark-active" class="form-check-label">{{ $t("enabled") }}</label>
</div>
<div class="form-text">
{{ $t("enableProxyDescription") }}
</div>
<br />
<div class="form-check form-switch">
<input id="mark-default" v-model="proxy.default" class="form-check-input" type="checkbox">
<label for="mark-default" class="form-check-label">{{ $t("setAsDefault") }}</label>
</div>
<div class="form-text">
{{ $t("setAsDefaultProxyDescription") }}
</div>
<br />
<div class="form-check form-switch">
<input id="apply-existing" v-model="proxy.applyExisting" class="form-check-input" type="checkbox">
<label class="form-check-label" for="apply-existing">{{ $t("Apply on all 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") }}
</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>
</div>
</div>
</div>
</form>
<Confirm ref="confirmDelete" btn-style="btn-danger" :yes-text="$t('Yes')" :no-text="$t('No')" @yes="deleteProxy">
{{ $t("deleteProxyMsg") }}
</Confirm>
</template>
<script lang="ts">
import { Modal } from "bootstrap";
import Confirm from "./Confirm.vue";
export default {
components: {
Confirm,
},
props: {},
emits: ["added"],
data() {
return {
model: null,
processing: false,
id: null,
proxy: {
protocol: null,
host: null,
port: null,
auth: false,
username: null,
password: null,
active: false,
default: false,
applyExisting: false,
}
};
},
mounted() {
this.modal = new Modal(this.$refs.modal);
},
methods: {
deleteConfirm() {
this.modal.hide();
this.$refs.confirmDelete.show();
},
show(proxyID) {
if (proxyID) {
this.id = proxyID;
for (let proxy of this.$root.proxyList) {
if (proxy.id === proxyID) {
this.proxy = proxy;
break;
}
}
} else {
this.id = null;
this.proxy = {
protocol: "https",
host: null,
port: null,
auth: false,
username: null,
password: null,
active: true,
default: false,
applyExisting: false,
};
}
this.modal.show();
},
submit() {
this.processing = true;
this.$root.getSocket().emit("addProxy", this.proxy, this.id, (res) => {
this.$root.toastRes(res);
this.processing = false;
if (res.ok) {
this.modal.hide();
// Emit added event, doesn't emit edit.
if (! this.id) {
this.$emit("added", res.id);
}
}
});
},
deleteProxy() {
this.processing = true;
this.$root.getSocket().emit("deleteProxy", this.id, (res) => {
this.$root.toastRes(res);
this.processing = false;
if (res.ok) {
this.modal.hide();
}
});
},
},
};
</script>
<style lang="scss" scoped>
@import "../assets/vars.scss";
.dark {
.modal-dialog .form-text, .modal-dialog p {
color: $dark-font-color;
}
}
</style>

View File

@@ -41,6 +41,9 @@
<Uptime :monitor="monitor.element" type="24" :pill="true" />
{{ monitor.element.name }}
</div>
<div v-if="showTags" class="tags">
<Tag v-for="tag in monitor.element.tags" :key="tag" :item="tag" :size="'sm'" />
</div>
</div>
<div :key="$root.userHeartbeatBar" class="col-3 col-md-4">
<HeartbeatBar size="small" :monitor-id="monitor.element.id" />
@@ -59,18 +62,23 @@
import Draggable from "vuedraggable";
import HeartbeatBar from "./HeartbeatBar.vue";
import Uptime from "./Uptime.vue";
import Tag from "./Tag.vue";
export default {
components: {
Draggable,
HeartbeatBar,
Uptime,
Tag,
},
props: {
editMode: {
type: Boolean,
required: true,
},
showTags: {
type: Boolean,
}
},
data() {
return {

View File

@@ -0,0 +1,67 @@
<template>
<div class="my-3 py-3">
<h5 @click="isOpen = !isOpen">
<div
class="
w-50
d-flex
justify-content-between
align-items-center
pe-2
"
>
<span class="pb-2">{{ heading }}</span>
<font-awesome-icon
icon="chevron-down"
class="animated"
:class="{ open: isOpen }"
/>
</div>
</h5>
<transition name="slide-fade-up">
<div v-if="isOpen" class="mt-3">
<slot></slot>
</div>
</transition>
</div>
</template>
<script>
export default {
props: {
heading: {
type: String,
default: "",
},
defaultOpen: {
type: Boolean,
default: false,
},
},
data() {
return {
isOpen: this.defaultOpen,
};
},
};
</script>
<style lang="scss" scoped>
@import "../assets/vars.scss";
h5:after {
content: "";
display: block;
width: 50%;
padding-top: 8px;
border-bottom: 1px solid $dark-border-color;
}
.open {
transform: rotate(180deg);
}
.animated {
transition: all 0.2s $easing-in;
}
</style>

View File

@@ -19,6 +19,19 @@
</div>
<p v-if="showURI && twoFAStatus == false" class="text-break mt-2">{{ uri }}</p>
<div v-if="!(uri && twoFAStatus == false)" class="mb-3">
<label for="current-password" class="form-label">
{{ $t("Current Password") }}
</label>
<input
id="current-password"
v-model="currentPassword"
type="password"
class="form-control"
required
/>
</div>
<button v-if="uri == null && twoFAStatus == false" class="btn btn-primary" type="button" @click="prepare2FA()">
{{ $t("Enable 2FA") }}
</button>
@@ -59,11 +72,11 @@
</template>
<script lang="ts">
import { Modal } from "bootstrap"
import { Modal } from "bootstrap";
import Confirm from "./Confirm.vue";
import VueQrcode from "vue-qrcode"
import { useToast } from "vue-toastification"
const toast = useToast()
import VueQrcode from "vue-qrcode";
import { useToast } from "vue-toastification";
const toast = useToast();
export default {
components: {
@@ -73,35 +86,36 @@ export default {
props: {},
data() {
return {
currentPassword: "",
processing: false,
uri: null,
tokenValid: false,
twoFAStatus: null,
token: null,
showURI: false,
}
};
},
mounted() {
this.modal = new Modal(this.$refs.modal)
this.modal = new Modal(this.$refs.modal);
this.getStatus();
},
methods: {
show() {
this.modal.show()
this.modal.show();
},
confirmEnableTwoFA() {
this.$refs.confirmEnableTwoFA.show()
this.$refs.confirmEnableTwoFA.show();
},
confirmDisableTwoFA() {
this.$refs.confirmDisableTwoFA.show()
this.$refs.confirmDisableTwoFA.show();
},
prepare2FA() {
this.processing = true;
this.$root.getSocket().emit("prepare2FA", (res) => {
this.$root.getSocket().emit("prepare2FA", this.currentPassword, (res) => {
this.processing = false;
if (res.ok) {
@@ -109,49 +123,51 @@ export default {
} else {
toast.error(res.msg);
}
})
});
},
save2FA() {
this.processing = true;
this.$root.getSocket().emit("save2FA", (res) => {
this.$root.getSocket().emit("save2FA", this.currentPassword, (res) => {
this.processing = false;
if (res.ok) {
this.$root.toastRes(res)
this.$root.toastRes(res);
this.getStatus();
this.currentPassword = "";
this.modal.hide();
} else {
toast.error(res.msg);
}
})
});
},
disable2FA() {
this.processing = true;
this.$root.getSocket().emit("disable2FA", (res) => {
this.$root.getSocket().emit("disable2FA", this.currentPassword, (res) => {
this.processing = false;
if (res.ok) {
this.$root.toastRes(res)
this.$root.toastRes(res);
this.getStatus();
this.currentPassword = "";
this.modal.hide();
} else {
toast.error(res.msg);
}
})
});
},
verifyToken() {
this.$root.getSocket().emit("verifyToken", this.token, (res) => {
this.$root.getSocket().emit("verifyToken", this.token, this.currentPassword, (res) => {
if (res.ok) {
this.tokenValid = res.valid;
} else {
toast.error(res.msg);
}
})
});
},
getStatus() {
@@ -161,10 +177,10 @@ export default {
} else {
toast.error(res.msg);
}
})
});
},
},
}
};
</script>
<style lang="scss" scoped>

View File

@@ -1,5 +1,5 @@
<template>
<span :class="className">{{ uptime }}</span>
<span :class="className" :title="24 + $t('-hour')">{{ uptime }}</span>
</template>
<script>

View File

@@ -0,0 +1,14 @@
<template>
<div class="mb-3">
<label for="alerta-api-endpoint" class="form-label">{{ $t("alertaApiEndpoint") }}</label>
<input id="alerta-api-endpoint" v-model="$parent.notification.alertaApiEndpoint" type="text" class="form-control" required>
<label for="alerta-environment" class="form-label">{{ $t("alertaEnvironment") }}</label>
<input id="alerta-environment" v-model="$parent.notification.alertaEnvironment" type="text" class="form-control" required>
<label for="alerta-api-key" class="form-label">{{ $t("alertaApiKey") }}</label>
<input id="alerta-api-key" v-model="$parent.notification.alertaApiKey" type="text" class="form-control" required>
<label for="alerta-alert-state" class="form-label">{{ $t("alertaAlertState") }}</label>
<input id="alerta-alert-state" v-model="$parent.notification.alertaAlertState" type="text" class="form-control" placeholder="critical" required>
<label for="alerta-recover-state" class="form-label">{{ $t("alertaRecoverState") }}</label>
<input id="alerta-recover-state" v-model="$parent.notification.alertaRecoverState" type="text" class="form-control" placeholder="cleared" required>
</div>
</template>

View File

@@ -6,7 +6,7 @@
<label for="secretAccessKey" class="form-label">{{ $t("SecretAccessKey") }}<span style="color: red;"><sup>*</sup></span></label>
<input id="secretAccessKey" v-model="$parent.notification.secretAccessKey" type="text" class="form-control" required>
<label for="phonenumber" class="form-label">{{ $t("Phonenumber") }}<span style="color: red;"><sup>*</sup></span></label>
<label for="phonenumber" class="form-label">{{ $t("PhoneNumbers") }}<span style="color: red;"><sup>*</sup></span></label>
<input id="phonenumber" v-model="$parent.notification.phonenumber" type="text" class="form-control" required>
<label for="templateCode" class="form-label">{{ $t("TemplateCode") }}<span style="color: red;"><sup>*</sup></span></label>
@@ -16,7 +16,7 @@
<input id="signName" v-model="$parent.notification.signName" type="text" class="form-control" required>
<div class="form-text">
<p>Sms template must contain parameters: <br> <code>${name} ${time} ${status} ${msg}</code></p>
<p>{{ $t("Sms template must contain parameters: ") }}<br> <code>${name} ${time} ${status} ${msg}</code></p>
<i18n-t tag="p" keypath="Read more:">
<a href="https://help.aliyun.com/document_detail/101414.html" target="_blank">https://help.aliyun.com/document_detail/101414.html</a>
</i18n-t>

View File

@@ -0,0 +1,15 @@
<template>
<div class="mb-3">
<label for="Bark Endpoint" class="form-label">{{ $t("Bark Endpoint") }}<span style="color: red;"><sup>*</sup></span></label>
<input id="Bark Endpoint" v-model="$parent.notification.barkEndpoint" type="text" class="form-control" required>
<div class="form-text">
<p><span style="color: red;"><sup>*</sup></span>{{ $t("Required") }}</p>
</div>
<i18n-t tag="div" keypath="wayToGetTeamsURL" class="form-text">
<a
href="https://github.com/Finb/Bark"
target="_blank"
>{{ $t("here") }}</a>
</i18n-t>
</div>
</template>

View File

@@ -0,0 +1,38 @@
<template>
<div class="mb-3">
<label for="clicksendsms-login" class="form-label">API Username</label>
<div class="form-text">
{{ $t("apiCredentials") }}
<a href="http://dashboard.clicksend.com/account/subaccounts" target="_blank">{{ $t("here") }}</a>
</div>
<input id="clicksendsms-login" v-model="$parent.notification.clicksendsmsLogin" type="text" class="form-control" required>
<label for="clicksendsms-key" class="form-label">API Key</label>
<HiddenInput id="clicksendsms-key" v-model="$parent.notification.clicksendsmsPassword" :required="true" autocomplete="one-time-code"></HiddenInput>
</div>
<div class="mb-3">
<div class="form-text">
{{ $t("checkPrice", [$t("clicksendsms")]) }}
<a href="https://www.clicksend.com/us/pricing" target="_blank">https://clicksend.com/us/pricing</a>
</div>
</div>
<div class="mb-3">
<label for="clicksendsms-to-number" class="form-label">Recipient Number</label>
<input id="clicksendsms-to-number" v-model="$parent.notification.clicksendsmsToNumber" type="text" minlength="8" maxlength="14" class="form-control" required>
</div>
<div class="mb-3">
<label for="clicksendsms-sender-name" class="form-label">From Name/Number -
<a href="https://help.clicksend.com/article/4kgj7krx00-what-is-a-sender-id-or-sender-number" target="_blank">More Info</a>
</label>
<input id="clicksendsms-sender-name" v-model="$parent.notification.clicksendsmsSenderName" type="text" minlength="3" maxlength="11" class="form-control">
<div class="form-text">Leave blank to use a shared sender number.</div>
</div>
</template>
<script>
import HiddenInput from "../HiddenInput.vue";
export default {
components: {
HiddenInput,
},
};
</script>

View File

@@ -7,9 +7,9 @@
<input id="secretKey" v-model="$parent.notification.secretKey" type="text" class="form-control" required>
<div class="form-text">
<p>For safety, must use secret key</p>
<p>{{ $t("For safety, must use secret key") }}</p>
<i18n-t tag="p" keypath="Read more:">
<a href="https://developers.dingtalk.com/document/robots/custom-robot-access" target="_blank">https://developers.dingtalk.com/document/robots/custom-robot-access</a>
<a href="https://developers.dingtalk.com/document/robots/custom-robot-access" target="_blank">https://developers.dingtalk.com/document/robots/custom-robot-access</a> <a href="https://open.dingtalk.com/document/robots/customize-robot-security-settings#title-7fs-kgs-36x" target="_blank">https://open.dingtalk.com/document/robots/customize-robot-security-settings#title-7fs-kgs-36x</a>
</i18n-t>
</div>
</div>

View File

@@ -0,0 +1,13 @@
<template>
<div class="mb-3">
<label for="google-chat-webhook-url" class="form-label">{{ $t("Webhook URL") }}<span style="color: red;"><sup>*</sup></span></label>
<input id="google-chat-webhook-url" v-model="$parent.notification.googleChatWebhookURL" type="text" class="form-control" required>
<div class="form-text">
<span style="color: red;"><sup>*</sup></span>{{ $t("Required") }}
<i18n-t tag="p" keypath="aboutWebhooks" style="margin-top: 8px;">
<a href="https://developers.google.com/chat/how-tos/webhooks" target="_blank">https://developers.google.com/chat/how-tos/webhooks</a>
</i18n-t>
</div>
</div>
</template>

View File

@@ -0,0 +1,51 @@
<template>
<div class="mb-3">
<label for="gorush-device-token" class="form-label">{{ $t("Device Token") }}</label><span style="color: red;"><sup>*</sup></span>
<div class="input-group mb-3">
<input id="gorush-device-token" v-model="$parent.notification.gorushDeviceToken" type="text" class="form-control" required>
</div>
</div>
<div class="mb-3">
<label for="gorush-server-url" class="form-label">{{ $t("Server URL") }}</label><span style="color: red;"><sup>*</sup></span>
<div class="input-group mb-3">
<input id="gorush-server-url" v-model="$parent.notification.gorushServerURL" type="text" class="form-control" required>
</div>
</div>
<div class="mb-3">
<label for="gorush-platform" class="form-label">{{ $t("Platform") }}</label><span style="color: red;"><sup>*</sup></span>
<select id="gorush-platform" v-model="$parent.notification.gorushPlatform" class="form-select">
<option value="ios">{{ $t("iOS") }}</option>
<option value="android">{{ $t("Android") }}</option>
<option value="huawei">{{ $t("Huawei") }}</option>
</select>
</div>
<div class="mb-3">
<label for="gorush-title" class="form-label">{{ $t("Title") }}</label>
<input id="gorush-title" v-model="$parent.notification.gorushTitle" type="text" class="form-control">
</div>
<div class="mb-3">
<label for="gorush-priority" class="form-label">{{ $t("Priority") }}</label>
<select id="gorush-priority" v-model="$parent.notification.gorushPriority" class="form-select">
<option value="normal">{{ $t("Normal") }}</option>
<option value="high">{{ $t("High") }}</option>
</select>
</div>
<div class="mb-3">
<label for="gorush-retry" class="form-label">{{ $t("Retry") }}</label>
<input id="gorush-retry" v-model="$parent.notification.gorushRetry" type="number" class="form-control">
</div>
<div class="mb-3">
<label for="gorush-topic" class="form-label">{{ $t("Topic") }}</label>
<input id="gorush-topic" v-model="$parent.notification.gorushTopic" type="text" class="form-control">
</div>
<div class="form-text">
<span style="color: red;"><sup>*</sup></span>{{ $t("Required") }}
</div>
</template>

View File

@@ -11,7 +11,7 @@
<div class="form-text">
<span style="color: red;"><sup>*</sup></span>{{ $t("Required") }}
<i18n-t tag="p" keypath="aboutWebhooks" style="margin-top: 8px;">
<a href="https://docs.rocket.chat/guides/administration/administration/integrations" target="_blank">https://api.slack.com/messaging/webhooks</a>
<a href="https://docs.rocket.chat/guides/administration/administration/integrations" target="_blank">https://docs.rocket.chat/guides/administration/administration/integrations</a>
</i18n-t>
<p style="margin-top: 8px;">
{{ $t("aboutChannelName", [$t("rocket.chat")]) }}

View File

@@ -1,82 +1,117 @@
<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">{{ $t("secureOptionNone") }}</option>
<option :value="true">{{ $t("secureOptionTLS") }}</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">
{{ $t("Ignore TLS Error") }}
</label>
<div>
<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>
<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="false" autocomplete="one-time-code"></HiddenInput>
</div>
<div class="mb-3">
<label for="from-email" class="form-label">{{ $t("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 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>
<div class="mb-3">
<label for="to-email" class="form-label">{{ $t("To Email") }}</label>
<input id="to-email" v-model="$parent.notification.smtpTo" type="text" class="form-control" autocomplete="false" placeholder="example2@kuma.pet, example3@kuma.pet" :required="!hasRecipient">
</div>
<div class="mb-3">
<label for="secure" class="form-label">{{ $t("Security") }}</label>
<select id="secure" v-model="$parent.notification.smtpSecure" class="form-select">
<option :value="false">{{ $t("secureOptionNone") }}</option>
<option :value="true">{{ $t("secureOptionTLS") }}</option>
</select>
</div>
<div class="mb-3">
<label for="to-cc" class="form-label">{{ $t("smtpCC") }}</label>
<input id="to-cc" v-model="$parent.notification.smtpCC" type="text" class="form-control" autocomplete="false" :required="!hasRecipient">
</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">
{{ $t("Ignore TLS Error") }}
</label>
</div>
</div>
<div class="mb-3">
<label for="to-bcc" class="form-label">{{ $t("smtpBCC") }}</label>
<input id="to-bcc" v-model="$parent.notification.smtpBCC" type="text" class="form-control" autocomplete="false" :required="!hasRecipient">
</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="subject-email" class="form-label">{{ $t("emailCustomSubject") }}</label>
<input id="subject-email" v-model="$parent.notification.customSubject" type="text" class="form-control" autocomplete="false" placeholder="">
<div v-pre class="form-text">
(leave blank for default one)<br />
{{NAME}}: Service Name<br />
{{HOSTNAME_OR_URL}}: Hostname or URL<br />
{{URL}}: URL<br />
{{STATUS}}: Status<br />
<div class="mb-3">
<label for="password" class="form-label">{{ $t("Password") }}</label>
<HiddenInput id="password" v-model="$parent.notification.smtpPassword" :required="false" autocomplete="one-time-code"></HiddenInput>
</div>
<div class="mb-3">
<label for="from-email" class="form-label">{{ $t("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">{{ $t("To Email") }}</label>
<input id="to-email" v-model="$parent.notification.smtpTo" type="text" class="form-control" autocomplete="false" placeholder="example2@kuma.pet, example3@kuma.pet" :required="!hasRecipient">
</div>
<div class="mb-3">
<label for="to-cc" class="form-label">{{ $t("smtpCC") }}</label>
<input id="to-cc" v-model="$parent.notification.smtpCC" type="text" class="form-control" autocomplete="false" :required="!hasRecipient">
</div>
<div class="mb-3">
<label for="to-bcc" class="form-label">{{ $t("smtpBCC") }}</label>
<input id="to-bcc" v-model="$parent.notification.smtpBCC" type="text" class="form-control" autocomplete="false" :required="!hasRecipient">
</div>
<ToggleSection :heading="$t('smtpDkimSettings')">
<i18n-t tag="div" keypath="smtpDkimDesc" class="form-text mb-3">
<a href="https://nodemailer.com/dkim/" target="_blank">{{ $t("documentation") }}</a>
</i18n-t>
<div class="mb-3">
<label for="dkim-domain" class="form-label">{{ $t("smtpDkimDomain") }}</label>
<input id="dkim-domain" v-model="$parent.notification.smtpDkimDomain" type="text" class="form-control" autocomplete="false" placeholder="example.com">
</div>
<div class="mb-3">
<label for="dkim-key-selector" class="form-label">{{ $t("smtpDkimKeySelector") }}</label>
<input id="dkim-key-selector" v-model="$parent.notification.smtpDkimKeySelector" type="text" class="form-control" autocomplete="false" placeholder="2017">
</div>
<div class="mb-3">
<label for="dkim-private-key" class="form-label">{{ $t("smtpDkimPrivateKey") }}</label>
<textarea id="dkim-private-key" v-model="$parent.notification.smtpDkimPrivateKey" rows="5" type="text" class="form-control" autocomplete="false" placeholder="-----BEGIN PRIVATE KEY-----"></textarea>
</div>
<div class="mb-3">
<label for="dkim-hash-algo" class="form-label">{{ $t("smtpDkimHashAlgo") }}</label>
<input id="dkim-hash-algo" v-model="$parent.notification.smtpDkimHashAlgo" type="text" class="form-control" autocomplete="false" placeholder="sha256">
</div>
<div class="mb-3">
<label for="dkim-header-fields" class="form-label">{{ $t("smtpDkimheaderFieldNames") }}</label>
<input id="dkim-header-fields" v-model="$parent.notification.smtpDkimheaderFieldNames" type="text" class="form-control" autocomplete="false" placeholder="message-id:date:from:to">
</div>
<div class="mb-3">
<label for="dkim-skip-fields" class="form-label">{{ $t("smtpDkimskipFields") }}</label>
<input id="dkim-skip-fields" v-model="$parent.notification.smtpDkimskipFields" type="text" class="form-control" autocomplete="false" placeholder="message-id:date">
</div>
</ToggleSection>
<div class="mb-3">
<label for="subject-email" class="form-label">{{ $t("emailCustomSubject") }}</label>
<input id="subject-email" v-model="$parent.notification.customSubject" type="text" class="form-control" autocomplete="false" placeholder="">
<div v-pre class="form-text">
(leave blank for default one)<br />
{{NAME}}: Service Name<br />
{{HOSTNAME_OR_URL}}: Hostname or URL<br />
{{URL}}: URL<br />
{{STATUS}}: Status<br />
</div>
</div>
</div>
</template>
<script>
import HiddenInput from "../HiddenInput.vue";
import ToggleSection from "../ToggleSection.vue";
export default {
components: {
HiddenInput,
ToggleSection,
},
computed: {
hasRecipient() {

View File

@@ -0,0 +1,28 @@
<template>
<div class="mb-3">
<label for="serwersms-username" class="form-label">{{ $t('serwersmsAPIUser') }}</label>
<input id="serwersms-username" v-model="$parent.notification.serwersmsUsername" type="text" class="form-control" required>
</div>
<div class="mb-3">
<label for="serwersms-key" class="form-label">{{ $t('serwersmsAPIPassword') }}</label>
<HiddenInput id="serwersms-key" v-model="$parent.notification.serwersmsPassword" :required="true" autocomplete="one-time-code"></HiddenInput>
</div>
<div class="mb-3">
<label for="serwersms-phone-number" class="form-label">{{ $t("serwersmsPhoneNumber") }}</label>
<input id="serwersms-phone-number" v-model="$parent.notification.serwersmsPhoneNumber" type="text" class="form-control" required>
</div>
<div class="mb-3">
<label for="serwersms-sender-name" class="form-label">{{ $t("serwersmsSenderName") }}</label>
<input id="serwersms-sender-name" v-model="$parent.notification.serwersmsSenderName" type="text" minlength="3" maxlength="11" class="form-control">
</div>
</template>
<script>
import HiddenInput from "../HiddenInput.vue";
export default {
components: {
HiddenInput,
},
};
</script>

View File

@@ -0,0 +1,13 @@
<template>
<div class="mb-3">
<label for="stackfield-webhook-url" class="form-label">{{ $t("Webhook URL") }}<span style="color: red;"><sup>*</sup></span></label>
<input id="stackfield-webhook-url" v-model="$parent.notification.stackfieldwebhookURL" type="text" class="form-control" required>
<div class="form-text">
<span style="color: red;"><sup>*</sup></span>{{ $t("Required") }}
<i18n-t tag="p" keypath="aboutWebhooks" style="margin-top: 8px;">
<a href="https://www.stackfield.com/developer-api#AnchorAPI2" target="_blank">https://www.stackfield.com/developer-api#AnchorAPI2</a>
</i18n-t>
</div>
</div>
</template>

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