Compare commits

...

681 Commits

Author SHA1 Message Date
Ioma Taani daef238a70
Updated Italian Language (#911) 3 years ago
Louis Lam 5df34cd137
Merge pull request #885 from ZegertBoele/patch-1 3 years ago
Louis Lam bf64095cea update to 1.10.2 3 years ago
Louis Lam 2333d1c7a7 Merge remote-tracking branch 'origin/master' 3 years ago
Louis Lam 95bae8289d Fix setting page when disabled auth 3 years ago
Louis Lam 45f7c647a6
Update feature_request.yaml 3 years ago
Zegert Boele dff1056bb1
Update nl-NL.js 3 years ago
Zegert Boele 62222c0336
Update src/languages/nl-NL.js 3 years ago
Louis Lam 733d0af75f update to 1.10.1 3 years ago
Louis Lam b88e74fad8
Merge pull request #891 from ivanbratovic/croatian-language 3 years ago
Ivan Bratović 734762b773 Merge with `master` 3 years ago
Louis Lam 0275d7a42b minor 3 years ago
Louis Lam 41a6d1b701 Fix parseCertificateInfo possibly in dead loop 3 years ago
Louis Lam c92153c97e add more debug msg 3 years ago
Louis Lam ad82ab0305
Merge pull request #883 from andreasbrett/patch-7 3 years ago
Louis Lam f952d283c6
Merge pull request #815 from Fallstop/tags-on-status 3 years ago
Louis Lam e164fabf81
Merge pull request #849 from Minvinea/master 3 years ago
Louis Lam bc69a331ee eslint 3 years ago
Jasper Miller-Waugh e4506963d9
Merge branch 'louislam:master' into tags-on-status 3 years ago
Zegert Boele 222540898b
Some translations were not translated 3 years ago
Andreas Brett 8f44b9f618 24h tooltip on status page 3 years ago
Louis Lam 210566c7af remove prefix for issue title, so users need to input the title 3 years ago
Louis Lam c5e6628803
Merge pull request #870 from chakflying/patch-3 3 years ago
Louis Lam 3a1d8ddc11
Merge pull request #869 from MrEddX/bulgarian 3 years ago
Nelson Chan bc5f61b3ec
Chore: Add drag and drop 3 years ago
Nelson Chan 57389fab2c
Update PULL_REQUEST_TEMPLATE.md 3 years ago
MrEddX ee2c54cfd1
Update bg-BG.js 3 years ago
Louis Lam 82cde7c847
Merge pull request #854 from 634750802/patch-1 3 years ago
Louis Lam 1ba2034701 freeze bootstrap to 5.1.3 to prevent breaking changes 3 years ago
Louis Lam dee131c25d fix table hover color not working after updated bootstrap to 5.1.3 3 years ago
Jasper Miller-Waugh e5d6410caf
Apply formatting suggestions from code review 3 years ago
Louis Lam e496c3b3be update healthcheck.js 3 years ago
Louis Lam 69f5112b38 Merge remote-tracking branch 'origin/master' 3 years ago
Louis Lam c094dc0c5b speed up redirect by using 302 redirect instead of vue redirect 3 years ago
Ivan Bratović 1fb9b25d13 Improve hr-HR translations 3 years ago
Ivan Bratović 9a135deac2 Add new translation for 'Uptime' 3 years ago
Your Name 8e6173c05e Fix and add new hr-HR translations 3 years ago
Louis Lam dec84282ed
Update bug_report.yaml 3 years ago
Louis Lam df80f413b5
Update feature_request.yaml 3 years ago
Louis Lam 17e59f1d8d
Update ask-for-help.yaml 3 years ago
Louis Lam 973c2bb429
Update ask-for-help.yaml 3 years ago
Louis Lam da0eaddeb8
Update SECURITY.md 3 years ago
Louis Lam b2bc8d9db9
Update bug_report.yaml 3 years ago
Louis Lam 541068ff3b
Update bug_report.yaml 3 years ago
Louis Lam 83ee46454a
Update bug_report.yaml 3 years ago
Louis Lam 75b21c905f
Merge pull request #847 from Saibamen/update_pl 3 years ago
Louis Lam 60e12f4bfa fix healthcheck.js with prefix UPTIME_KUMA_ 3 years ago
Jasper Miller-Waugh 191b81ee07
Fix grammer in comment 3 years ago
Jagger f3651a1219
Add a status prefix for feishu notification 3 years ago
Jasper Miller-Waugh 12ef9f39c5
Merged buttons, cleaned up SS tag retrieval and made tagsVisible a bool. 3 years ago
Jasper Miller-Waugh 4004926e64
Small formatting changes from code-review 3 years ago
Adam Stachowicz 4d3d6d6e25 Missing this... 3 years ago
Adam Stachowicz d06e5ef6fa More small letters 3 years ago
Adam Stachowicz b12b848d97 One more typo... 3 years ago
Adam Stachowicz bb96a577ca Fix typos + translate `wayToGetTeamsURL` 3 years ago
Adam Stachowicz 8840ca618b Update `pl.js` 3 years ago
Minvinea 8ec858fd14
Add missing translation 3 years ago
Louis Lam 124c98ce76 update to 1.10.0 3 years ago
Louis Lam 61135e8500 update package-lock.json 3 years ago
Louis Lam 08a58dec2b fix manifest.json path 3 years ago
Louis Lam 741ed548da Merge branch 'master' into use_hideCount_option 3 years ago
Louis Lam 52d80d3a5d Merge remote-tracking branch 'origin/master' 3 years ago
Louis Lam 586c748d44 vite build on host machine, since it is very slow on armv7/arm64 build 3 years ago
Louis Lam b5d6e96b1d
Merge pull request #842 from gaby/issue-form-updates 3 years ago
Juan Calderon-Perez 68b74f07e4 Update wording of form field 3 years ago
Louis Lam bc615c2dd8
Merge pull request #832 from ivanbratovic/croatian-language 3 years ago
Louis Lam e7104737e7 prevent to show "New Update" when the Docker image is not ready yet 3 years ago
Louis Lam 1dbf1c3dea fix logout 3 years ago
Jasper Miller-Waugh 74688e69aa
Remove debug statement in server/routers/api-router.js 3 years ago
Jasper Miller-Waugh b32bfb3ff1
Added toggle for tag visibility 3 years ago
Jasper Miller-Waugh 24664cde2c
Smarter CSS to fix Mobile alignment 3 years ago
Jasper Miller-Waugh 348c5ec995
Match lint settings 3 years ago
Jasper Miller-Waugh 9143b73f84
Styling for tags 3 years ago
Jasper Miller-Waugh 5e6d945095
Most hacked in POC 3 years ago
Louis ba93129b18 Merge branch '1.9.X' 3 years ago
Louis cf548df15f update to 1.9.2 3 years ago
Louis caa2a34177 fix 2fa not working #833 3 years ago
Adam Stachowicz 69aa60d1fb Add missing i18n key + translate 2 keys for polish 3 years ago
Ivan Bratović eaecd6e571 Clarity and grammar fixes to hr_HR languange file 3 years ago
Adam Stachowicz f2a27a2cf1 Use `hideCount` option for pagination 3 years ago
Louis d4c9431142 catch createWriteStream error 3 years ago
Louis d7f7dba13f write some error logs to ./data/error.log 3 years ago
Louis 3e5ae00d25 update dependencies 3 years ago
Louis Lam 5311bef3eb
Merge pull request #809 from Saibamen/pagination_no_text 3 years ago
Louis Lam c400595f67
Merge pull request #825 from SiderealArt/patch-1 3 years ago
Louis Lam e84f7dac60
Merge pull request #821 from ivanbratovic/croatian-language 3 years ago
SiderealArt 67a22399bc
Update i18n.js 3 years ago
SiderealArt 947fc6001e
Update Settings.vue 3 years ago
SiderealArt c87e67ad1b
Add Traditional Chinese (Taiwan) translation 3 years ago
Ivan Bratović 6f92774a8f ESLint pass on hr-HR.js file 3 years ago
Ivan Bratović 6e76ab7426 Import hr-HR language in i18n.js 3 years ago
Ivan Bratović b2fbd7e263 Add confirmDisableAuth tranlation for hr-HR 3 years ago
Ivan Bratović 199e6ec82b Add initial version of Croatian language 3 years ago
Adam Stachowicz 18a99c2016 Revert deleting `records` and `"One record"` keys 3 years ago
Louis Lam e261a27ebe fix wrong call of rejectUnauthorized for smtp (#757) 3 years ago
Louis Lam de5cce9d90 Steam API Key to HiddenInput 3 years ago
Louis Lam b85c9186f9
Merge pull request #781 from louislam/cert-notification 3 years ago
Louis Lam eb22ad5ffe [certificate notification] error handling and better msg 3 years ago
Louis Lam f5f4835b74 [certificate notification] clear sent history if the cert is changed 3 years ago
Louis Lam 44c1b336dc send certificate notifications in 21, 14, 7 days 3 years ago
Louis Lam 110ec491ee Merge branch 'master' into cert-notification 3 years ago
Louis Lam 640b6e5b1c prevent monitor dead for unexpected error 3 years ago
Louis Lam 234fba3978
Update SECURITY.md 3 years ago
Louis Lam 1285ccb537
Merge pull request #784 from gaby/issue-forms 3 years ago
Juan Calderon-Perez 6c542edfc9 Add examples for version and image tag. 3 years ago
Juan Calderon-Perez 767807dd22 Added text placeholders for versions. 3 years ago
Juan Calderon-Perez 546402f3d2 Added docker-image-tag field. 3 years ago
Louis Lam 698a38e773 clear status page cache in a better place 3 years ago
Louis Lam 71884cf42a
Merge pull request #772 from andreasbrett/patch-6 3 years ago
Louis Lam d676c782bb
Update README.md 3 years ago
Juan Calderon-Perez dd773aa5a2 Updates based on PR comments. 3 years ago
Louis Lam 2852e59ffb
Merge pull request #812 from MrEddX/bulgarian 3 years ago
MrEddX cb3da50e7e
Update bg-BG.js 3 years ago
Louis Lam f25653d778
Update README.md 3 years ago
Adam Stachowicz 2e7ad1b7b2 Don't display "count" text for pagination 3 years ago
Louis Lam e0e1ab6fa6
Update CONTRIBUTING.md 3 years ago
Louis Lam 8d984881c9
Update CONTRIBUTING.md 3 years ago
Louis Lam 955f9ae20a
Merge pull request #794 from louislam/free-disk-space 3 years ago
Louis Lam a9e319517a add auto vacuum and shrink database button 3 years ago
Louis Lam 39ad8b4bb7 Merge branch 'master' into free-disk-space 3 years ago
Andreas Brett 8fb8cbdaf3 use auth0/jwt-decode 3 years ago
Andreas Brett 3f3d8b4eb3 fixes 3 years ago
Louis Lam 9123e9461f
Update CONTRIBUTING.md 3 years ago
Louis Lam 77addfebc8 Merge branch 'tarun7singh_master' 3 years ago
Louis Lam 16846c7c6d
Merge pull request #790 from Saibamen/pl_update+fixes 3 years ago
Louis Lam d1c4d13903
Merge pull request #782 from Co2333/dev-lakr233-bark_notification 3 years ago
Louis Lam 7cd4bfc11d
Merge pull request #806 from Saibamen/patch-1 3 years ago
Adam Stachowicz 1d5c0502ab
Add other 3 years ago
Adam Stachowicz a1cda93ad5
Update PULL_REQUEST_TEMPLATE.md 3 years ago
Louis Lam 3bd420f0e0
Merge pull request #752 from chakflying/feat/chart-period 3 years ago
Louis Lam 78424b4f2d add simple loading chart effect 3 years ago
Louis Lam f8055ed03d minor css 3 years ago
Louis Lam fe4724fc53 Merge branch 'master' into feat/chart-period 3 years ago
Louis Lam 7f0dda6a44
Update CONTRIBUTING.md 3 years ago
Lakr Aream 43791ee97e Merge branch 'master' into dev-lakr233-bark_notification 3 years ago
Tarun Singh 6362ef6a9c removed other langauge translation data 3 years ago
Adam Stachowicz 9d3a4e9d1e Remove dot from `needPushEvery` 3 years ago
Tarun Singh 6c60096f56
Merge branch 'louislam:master' into master 3 years ago
Tarun Singh ba1e025353 added click send to number 3 years ago
Louis Lam a41a081727 Merge remote-tracking branch 'origin/master' 3 years ago
Louis Lam a5f15f2319 Fix #793, check dist-backup existing before delete 3 years ago
Louis Lam e69799f613
Update CONTRIBUTING.md 3 years ago
Louis Lam 3c795bebe3
Update CONTRIBUTING.md 3 years ago
Louis Lam 3a43fec666
add recommend pull request guideline 3 years ago
Louis Lam 24c645e437 [empty commit] pull request for free-disk-space 3 years ago
Louis Lam 9d31da1fe8
Merge pull request #792 from gaby/stalebot-fix 3 years ago
Juan Calderon-Perez 2e4c42941a
Rename stale-bot to stale-bot.yml 3 years ago
Andreas Brett 4fc2603818 Merge branch 'patch-6' of https://github.com/andreasbrett/uptime-kuma into patch-6 3 years ago
Andreas Brett 7bc38d4231 remove unused vars 3 years ago
Andreas Brett daad63d70b
Merge branch 'master' into patch-6 3 years ago
Andreas Brett 9ddd2c7365 use jwt 3 years ago
Adam Stachowicz fa5ba12e14 Missing this 3 years ago
Adam Stachowicz 85053f865e Fix typo 3 years ago
Adam Stachowicz 1239f6d1a2 PL update + fixes 3 years ago
Tarun Singh fed611d1b9 Merge branch 'master' of https://github.com/louislam/uptime-kuma 3 years ago
Tarun Singh bc68088350 Click send sms integration for notifications 3 years ago
Louis Lam 90200958cd
Merge pull request #780 from kry008/patch-4 3 years ago
Louis Lam aa13d74d7a
Merge pull request #788 from Ponkhy/german-language 3 years ago
Ponkhy d82f305f6e Updated german language file 3 years ago
Louis Lam 7c63cbfd84 add node.js 17 to auto test 3 years ago
Juan Calderon-Perez c7e1267779 More syntax changes. Change What to Which 3 years ago
Louis Lam 5d0b54c292
Merge pull request #785 from Saibamen/patch-1 3 years ago
Juan Calderon-Perez b50b390048 Changes based on PR requests 3 years ago
Adam Stachowicz 65158cb06b
Update PULL_REQUEST_TEMPLATE.md 3 years ago
Adam Stachowicz 8fe5e4e605
Fix lint 3 years ago
Adam Stachowicz ab5ddae2ee
Delete double new line 3 years ago
Adam Stachowicz 89c64f4ea2
Create PULL_REQUEST_TEMPLATE.md 3 years ago
Juan Calderon-Perez 40a1ebecc5
Update feature_request.yaml 3 years ago
Juan Calderon-Perez e1793596fe
Merge pull request #1 from gaby/issue-forms 3 years ago
Juan Calderon-Perez c489058a57 Update labels for each form 3 years ago
Juan Calderon-Perez 95342ec006 Update label for ask for help 3 years ago
Juan Calderon-Perez bdebbf8e40 Add Support for Issue Forms 3 years ago
Juan Calderon-Perez 9a9fca67d5 Migrate Bug Report to Github Issue Forms 3 years ago
Nelson Chan 665bae0806 UI: Simplify dropdown design 3 years ago
Nelson Chan e4be28a9e7 Fix: Validate beat time before appending 3 years ago
Nelson Chan 445674aacb Chore: Improve code formatting & comments 3 years ago
Nelson Chan 2f7b60f5e5 Feat: Use separate storage for custom chart period 3 years ago
Nelson Chan b83c59e308 WIP: Add options for chart period 3 years ago
Lakr Aream ce852dfa02 Support for Bark (APN) notifications 3 years ago
Louis Lam c9549c0de2 change body and header placeholders, less misleading. 3 years ago
Louis Lam 957c292307 add certificate-notification job 3 years ago
kry008 b5eb17ed93
Update pl.js 3 years ago
Louis Lam d578300104
Merge pull request #743 from andreasbrett/patch-4 3 years ago
Louis Lam b77b33e790 add login rate limiter 3 years ago
Andreas Brett 4becb97a5d
Update en.js 3 years ago
Andreas Brett 85e2b36424
Update en.js 3 years ago
Andreas Brett abdf1ae90a
Update en.js 3 years ago
Andreas Brett 606c967985
Merge branch 'master' into patch-6 3 years ago
Andreas Brett 93c231b4d9
Update server/server.js 3 years ago
Andreas Brett 9ad8e5f56a show logged in user 3 years ago
Louis Lam 8a481a1be0
Merge pull request #769 from andreasbrett/patch-5 3 years ago
Andreas Brett 657987a013 fix: show beat.msg only if available 3 years ago
Louis Lam d74577608b
Merge pull request #766 from DX37/translation-ru 3 years ago
Louis Lam 20a399c557
Merge pull request #767 from mrphuongbn/master 3 years ago
Phuong Nguyen Minh 060dde9827
Merge branch 'louislam:master' into master 3 years ago
DX37 1d1601cf24
Update notifications area in ru-RU.js 3 years ago
Louis Lam ff5f2e8dfb add limiter 3 years ago
Louis Lam 5451fb7672 Merge remote-tracking branch 'origin/master' 3 years ago
Louis Lam 56094a43d7 add passwordStrength 3 years ago
Louis Lam 68bbe8944a
Merge pull request #756 from DX37/translation-ru 3 years ago
Louis Lam 8f1da6aa22
Update CONTRIBUTING.md 3 years ago
Louis Lam c0d6fe0d76
Update CONTRIBUTING.md 3 years ago
DX37 29e4e41215
Update ru-RU.js 3 years ago
Louis Lam 7a1bb964e9
Update bug_report.md 3 years ago
Louis Lam 3fe0e9bf1e
Update ask-for-help.md 3 years ago
Louis Lam 9982887783
Update feature_request.md 3 years ago
Louis Lam c31efc0ef4
Merge pull request #748 from MrEddX/bulgarian 3 years ago
MrEddX 6463d4b209
Fix: Integration Link Text 3 years ago
Louis Lam acada8028a
Merge pull request #747 from MrEddX/bulgarian 3 years ago
MrEddX d0b0c64b81
Update bg-BG.js 3 years ago
Louis Lam cd04ac4557 "dist/index.html" is no longer needed for development environment 3 years ago
Louis Lam d7d2f7b7fc
Merge pull request #745 from dpatrongomez/patch-2 3 years ago
Daniel Patrón Gómez 5a05d135b8
Add steam translation in spanish 3 years ago
Louis Lam e03ee593e2
Merge pull request #716 from NeuralMiner/textchanges 3 years ago
Louis Lam 6c1ee70e15 fix 3 years ago
Louis Lam 5c3892313e add env var: UPTIME_KUMA_DISABLE_FRAME_SAMEORIGIN 3 years ago
Louis Lam c57c94642c Merge remote-tracking branch 'origin/master' 3 years ago
Louis Lam 62f168a2a5 config response header 3 years ago
Louis Lam c808f78f09
Merge pull request #735 from gaby/stale-bot 3 years ago
Louis Lam 9c80e1c732
Merge pull request #641 from andreasbrett/patch-1 3 years ago
Andreas Brett acc2995d86 invalidate used token 3 years ago
Andreas Brett 7def9dcec7
Merge branch 'louislam:master' into patch-1 3 years ago
NeuralMiner a35569481d Updates 3 years ago
NeuralMiner 9ddffc0f7f Updates 3 years ago
NeuralMiner 76e7c8b276 Rebase 3 years ago
NeuralMiner 572a5300aa Recommended updates. 3 years ago
NeuralMiner e1f1d4a959
Merge branch 'louislam:master' into textchanges 3 years ago
Louis Lam c6fc385289 update to 1.9.1 3 years ago
Louis Lam c645658161 Merge remote-tracking branch 'origin/master' 3 years ago
Louis Lam 182597944d fix #721 3 years ago
Louis Lam 8eaa8116c3
update email 3 years ago
Louis Lam 3512faad14 move `kubernetes` folder to the `k8s-unofficial` branch 3 years ago
Louis Lam f11417e854 [upload artifacts] no idea why suddenly not working via env var, hardcode the VERSION instead 3 years ago
Juan Calderon-Perez b5857f7c0c
Fix syntax issue. 3 years ago
Juan Calderon-Perez 6277babf25
Migrate to actions/stale v4.0 3 years ago
Louis Lam 5f36d2acda fix upload artifacts 3 years ago
Louis Lam cc36ff5210 update to 1.9.0 3 years ago
Louis Lam c363d3374e fix "build-docker-nightly-alpine" wrong path 3 years ago
Louis Lam 65a8cb5307
Merge pull request #738 from NixNotCastey/promosms 3 years ago
Lukas f74b2662c5 Fixed values for sms type 3 years ago
Louis Lam 300a95d779 recompile util.js with tsconfig.json 3 years ago
Louis Lam 23714ab688 genSecret don't need `await` 3 years ago
Louis Lam 16b44001e7 Merge remote-tracking branch 'andreasbrett/patch-1' into andreasbrett_patch-1 3 years ago
Louis Lam f2f8f33b86 Merge branch 'master' into andreasbrett_patch-1 3 years ago
Louis Lam 6e18f39eb4 [steam] code cleanup 3 years ago
Louis Lam 68d44dd9b3 [steam] do not request if there is no steam api key 3 years ago
Louis Lam 20d59e5a13 fix and move the steam api key to settings page 3 years ago
Louis Lam ae31eb6ba9 Merge branch 'master' into Revyn112_master 3 years ago
Andreas Brett df4682d19b
Merge branch 'master' into patch-1 3 years ago
Andreas Brett 11a1f35cc5 independent csprng solution 3 years ago
Juan Calderon-Perez 2a3ce15328
Use default number of operations per day. Defaults to 30 3 years ago
Juan Calderon-Perez 7cb25255bf
Update stale-bot 3 years ago
Juan Calderon-Perez c622f7958f
Add support for closing stale Issues/PR 3 years ago
Louis Lam df5efcc71c
Merge pull request #730 from sargonas/sargonas-patch-1 3 years ago
J. Eckert 4cd66b20b1
Update README.md 3 years ago
J. Eckert 1276102c18
Update README.md 3 years ago
Louis Lam 6944b35ea7
Merge pull request #667 from zsxeee/i18n 3 years ago
Louis Lam 88757ebbbe
Merge pull request #722 from dpatrongomez/master 3 years ago
Daniel Patrón Gómez 0a73b84ae6 Add records translations and fix pause translation 3 years ago
Daniel Patrón Gómez 15f36f96c3 Add spanish translation for monitor history 3 years ago
Louis Lam edcaf93446
Merge pull request #721 from dpatrongomez/master 3 years ago
Louis Lam dec175d55f
Merge pull request #719 from bertyhell/bugfix/edit-monitor 3 years ago
Louis Lam 9a60f69f66
Merge pull request #720 from bertyhell/bugfix/fix-error-on-first-hearthbeat 3 years ago
Daniel Patrón Gómez 53a008ae2b Add Status Translation 3 years ago
Bert Verhelst 1d63dd9ddd fix(monitor): safely get status of previous beat if first beat 3 years ago
Bert Verhelst 61627545a5 fix(edit-monitor): no need to escape placeholder {} if not translated 3 years ago
Louis Lam 176fa6b60d merge package-lock.json 3 years ago
Louis Lam cb43ecb46e Merge branch 'master' into background-jobs 3 years ago
Louis Lam 2e24312f67 [test] jest please 3 years ago
Louis Lam 6ff3cb275e
Merge pull request #642 from andreasbrett/patch-2 3 years ago
Louis Lam 9d364b28b1
Merge pull request #715 from januridp/master 3 years ago
Louis Lam bc3e3f9118 [test] update 3 years ago
Louis Lam 0f3ab7b1d8 [test] increase the timeout for reset-password 3 years ago
NeuralMiner 8cb26d2b31 Text update 3 years ago
januridp d94fbede32 Fix(Language): Bahasa Indonesia (Indonesian) 3 years ago
januridp 76e619c066 Fix(Language): Bahasa Indonesia (Indonesian) 3 years ago
januridp 4e4f94ab98 Fix(Language): Bahasa Indonesia (Indonesian) 3 years ago
januridp ed3a558397 Fix(Language): Bahasa Indonesia (Indonesian) 3 years ago
Louis Lam a419aa527f Merge remote-tracking branch 'origin/master' 3 years ago
Louis Lam 4d26825cbe [test] reset-password 3 years ago
Louis Lam 7276f34d90 fix reset-password 3 years ago
Louis Lam e1eeb44e7f
Update SECURITY.md 3 years ago
Louis Lam f4b8da0a5c Merge branch 'feature/add-support-for-method-body-and-headers' 3 years ago
Louis Lam 4178983df3 Merge remote-tracking branch 'origin/master' 3 years ago
Louis Lam 7ac0ab2e34 [http options] beautify the json format when clicked the save button 3 years ago
Louis Lam cd211a6be7 [http options] fine tune 3 years ago
Louis Lam 4e71ab7406 Merge branch 'master' into feature/add-support-for-method-body-and-headers 3 years ago
Louis Lam 76c68071f1
Merge pull request #709 from iooner/patch-1 3 years ago
Phuong Nguyen Minh bda481c61e
Update vi.js 3 years ago
iooner 8242a1586d
Update fr-FR.js 3 years ago
Louis Lam c593a962c2
Merge pull request #627 from NixNotCastey/smtp-subject 3 years ago
Louis Lam c9b4d2ae2a
Merge pull request #698 from erktime/master 3 years ago
Louis Lam 37105d720b
Merge pull request #706 from firattemel/master 3 years ago
Louis Lam 3b74b727f2 [Push Type] fix missing important flag and missing up notification 3 years ago
firattemel 2f0119bc3f Update tr-TR.js 3 years ago
Louis Lam a7d2a34dae fix ping bug 3 years ago
Louis Lam 60acb91fc8
Merge pull request #687 from xjoker/master 3 years ago
Louis Lam f51156f18e run eslint for #687 3 years ago
Louis Lam 8338881927 [SMTP] change {{HOSTNAME}} to {{HOSTNAME_OR_URL}}, support for http montior type, some UI improvements 3 years ago
Louis Lam 674b387c95 Merge branch 'master' into smtp-subject 3 years ago
Louis Lam 5ff9a64e5e [Push Type] Fix missing duration calculation (#685) 3 years ago
Louis Lam 4bee57ea7f Merge remote-tracking branch 'giacomo892/patch-1' 3 years ago
Louis Lam f75c9e4f0c add UPTIME_KUMA_HOST, UPTIME_KUMA_PORT and special handling for FreeBSD 3 years ago
xJoker 8ab4788f80
Update src/components/notifications/index.js 3 years ago
xJoker 4e4ab0577e
Update src/components/notifications/index.js 3 years ago
xJoker 6e04ec436e
Update server/notification-providers/dingding.js 3 years ago
xJoker 2d471a5e84
Update server/notification-providers/dingding.js 3 years ago
xJoker cae194f58f
Update server/notification-providers/dingding.js 3 years ago
Aaron Erkenswick 655ccc86b9 Add monitor name context to Slack fallback text. 3 years ago
Louis Lam e2dbacb383 Fix encoding problem of ping result for non-English Windows 3 years ago
Lukas 89b34b5748 Use double curly brackets and sanity check for customSubject 3 years ago
Andreas Brett 86dcc9bc8f import webcrypto lib 3 years ago
Louis Lam 9b05e86c25 set newLine to LF for ts compiler 3 years ago
Louis Lam 145b722aec Merge branch 'master' into andreasbrett_patch-1 3 years ago
Louis Lam 2ff7c4de5d [test] genSecret 3 years ago
Louis Lam 79c81395bc Merge branch 'master' into andreasbrett_patch-1 3 years ago
Louis Lam 178e5cd2c0
Merge pull request #689 from hrtkpf/master 3 years ago
hrtkpf 84507268ad fix translations (de-DE and en) 3 years ago
wuwenjing 843992c410 Add DingDing notification 3 years ago
zsxeee 33f773fcd0
Move param out of the translation file 3 years ago
zsxeee 26841a64f0
Update zh-CN.js 3 years ago
Phuong Nguyen Minh 89c0f8b734
Update i18n.js 3 years ago
wuwenjing 57a76e6129 remove `alicloud/pop-core` keep simple 3 years ago
Phuong Nguyen Minh dc805cff97
Add files via upload 3 years ago
giacomo892 3fe3450533
Prioritize port passed from args 3 years ago
Lukas 330cd6e058
Minor rehabilitanty impedyment 3 years ago
wuwenjing a2f2253221 Add aliyun sms notification 3 years ago
Louis Lam 1e5ce92917
Merge pull request #678 from wellart/master 3 years ago
Louis Lam 0d7c2960b0
Merge pull request #683 from Saibamen/fix_markdown 3 years ago
Louis Lam 82343de972
Merge pull request #682 from Saibamen/patch-1 3 years ago
Adam Stachowicz 521d57c483 Fix some of markdownlint warnings 3 years ago
Adam Stachowicz 281671b938
Fix build 3 years ago
Lukas 30d8aadf12 Slightly refactor 3 years ago
KangAlleW 2939bd4138 fix locale 3 years ago
KangAlleW dcf15c3eb7 edit i18n.js 3 years ago
KangAlleW 7c9ed98408 rename id.js to id-ID.js 3 years ago
KangAlleW 4b9f0a3fe6 add indonesian language and edit settings.vue 3 years ago
KangAlleW 5b67fec084 add indonesian language 3 years ago
Louis Lam 407581ee07 move jest config files to config dir 3 years ago
Louis Lam 11c3c636e0 move dockerfile, docker-compose.yml to docker folder 3 years ago
Louis Lam 911d4ea37b move vite.config.js to config folder 3 years ago
Louis Lam 4039c6549e Merge remote-tracking branch 'origin/master' 3 years ago
giacomo892 d733ec018e
Prioritize host arg 3 years ago
Nelson Chan 03b07730d3 Fix: Increase default kept period 3 years ago
Louis Lam dbc87d8ab3
Merge pull request #670 from dhfhfk/master 3 years ago
dhfhfk 05b691d4c9
Fix typo 3 years ago
Louis Lam 029d6412da
Merge pull request #669 from pemassi/patch-2 3 years ago
Louis Lam 9f12f95cce
Merge pull request #666 from Rohlik/patch-1 3 years ago
Kyungyoon Kim c1112a32df
Update ko-KR.js 3 years ago
Kyungyoon Kim efc78acfeb
Update ko-KR.js 3 years ago
zsxeee 9e01959d15
Update zh-CN.js 3 years ago
zsxeee 3fe91c52cb
Fix i18n 3 years ago
Tomas Rohrer 5269dcec60
Fix length 3 years ago
zsxeee a2cc7d1db9
Avoid directory not found error 3 years ago
Louis Lam 18c5a16783
Update README.md 3 years ago
Andreas Brett 2538bd04ce notp verification defaults 3 years ago
Louis Lam b7528b9a4e
Merge pull request #657 from thomasleveil/patch-2 3 years ago
Thomas LÉVEIL 9c058054b9 [i18n] minor update to french translations 3 years ago
Louis Lam c6683e2a9b
Merge pull request #648 from atlochowski/patch-1 3 years ago
Louis Lam b558708be2
Merge pull request #650 from xjoker/master 3 years ago
Louis Lam fd0dd2d284
Merge pull request #652 from GhostSlayer/patch-1 3 years ago
xJoker 1bc77a06e5
Update server/notification-providers/feishu.js 3 years ago
xJoker 69c623ac2b
Update server/notification-providers/feishu.js 3 years ago
Atlochowski 69ffee55dd
Update pl.js 3 years ago
Slayer d769c4426c
Create PM2 Config file 3 years ago
Atlochowski ebf0671fef
Update pl.js 3 years ago
Louis Lam 97af09fd50
Update README.md 3 years ago
Louis Lam 5b19e3f025
Update README.md 3 years ago
wuwenjing ce2df137e6 change text to using variable `msg` 3 years ago
wuwenjing 6d9b71c054 Add Feishu notification 3 years ago
Atlochowski a433de74e6
Update pl.js 3 years ago
LouisLam 8e3f43d60b Merge remote-tracking branch 'origin/master' 3 years ago
Louis Lam 2a1fd93444
Merge pull request #643 from andreasbrett/patch-3 3 years ago
Andreas Brett dc1de50a02 fix for max-inclusive 3 years ago
Andreas Brett e223e826a3 linting 3 years ago
Andreas Brett 0e6d7694ce
Update util.js 3 years ago
Andreas Brett 503d1f0a91
translation fixes 3 years ago
Andreas Brett 11bcd1e2ed
const 3 years ago
Andreas Brett 06310423f4
typo 3 years ago
Andreas Brett e127e168b6
typed parameters 3 years ago
Andreas Brett b5b391c73b
avoid default values for token verification 3 years ago
Andreas Brett 075535ba46
Update util.ts 3 years ago
Andreas Brett 13cf6891ac
cryptographically strong secret generation 3 years ago
Louis Lam ad0cde6554
Merge pull request #633 from dhfhfk/master 3 years ago
Louis Lam 25d18f0da3
Merge pull request #636 from DanielRTRD/add-norwegian-lang 3 years ago
RisedSky e9445bb2e3
Merge pull request #1 from RisedSky/RisedSky-patch-FR-Lang 3 years ago
RisedSky ecc25ba596
Update fr-FR.js 3 years ago
LouisLam e5286b0973 eslint for ko-KR.js 3 years ago
LouisLam 4e94cb9aad fix upload dist path 3 years ago
Daniel S. Billing 037fdd73a3 Norsk 3 years ago
Daniel S. Billing bb9a936658 Translated some of the notifications services 3 years ago
Bert Verhelst 5445c2a2ff fix(monitor): revert unintentional change to comment 3 years ago
Bert Verhelst dc08510e72 Merge remote-tracking branch 'origin/master' into feature/add-support-for-method-body-and-headers 3 years ago
Daniel S. Billing 62805014df Update Settings.vue 3 years ago
Daniel S. Billing c79e80442a Update i18n.js 3 years ago
Daniel S. Billing f0ff96afd9 Create nb-NO.js 3 years ago
Louis Lam 8083368a81
Merge pull request #634 from RisedSky/patch-1 3 years ago
RisedSky afb75e07d5
Update fr-FR.js 3 years ago
RisedSky efd3822930
Update fr-FR.js 3 years ago
dhfhfk 2adac64c83
Update ko-KR.js 3 years ago
Louis cee225bcb2 no idea why npm prune --production suddenly not working, switch to npm ci --production 3 years ago
Louis 8958c21736 Merge remote-tracking branch 'origin/master' 3 years ago
Louis 2286f78f57 update to 1.8.0 3 years ago
Louis d9eab90a69 Merge branch 'no-need-build' 3 years ago
Louis Lam 4ba2025451
minor 3 years ago
LouisLam 272d4bde45 find promossms language key typo 3 years ago
LouisLam 0550ceb6d4 Merge remote-tracking branch 'origin/master' 3 years ago
LouisLam 82131f4dd2 merge conflict 3 years ago
Louis Lam 0c88e4b2f6
Merge pull request #629 from NixNotCastey/pl-update 3 years ago
Louis Lam 5c6230da58
Merge pull request #630 from xinac721/master 3 years ago
Louis Lam d6753b8833
Update SECURITY.md 3 years ago
Louis Lam 93a021d027
Update SECURITY.md 3 years ago
新逸Cary 54f864a1bb Update Simplified Chinese Language(更新简体中文语言) 3 years ago
Lukas d6f7be9112 Translated all missing texts and updated few of previously translated. 3 years ago
Bert Verhelst 5137c80c07 fix(monitor): handle empty headers 3 years ago
Lukas 792f3c7c5c Add support for values of Name, Hostname and Status 3 years ago
Bert Verhelst 8a739af5ad Merge remote-tracking branch 'origin/master' into feature/add-support-for-method-body-and-headers 3 years ago
Lukas edb75808d8
Merge branch 'louislam:master' into smtp-subject 3 years ago
LouisLam 56ae6f6117 fix demoMode export 3 years ago
Lukas 5e3ea3293c Very basic email subject customization 3 years ago
LouisLam 5c89562650 not allow lower than 20s for demo mode 3 years ago
Louis Lam 1f7f20526f
Merge pull request #620 from MrEddX/bulgarian 3 years ago
Louis Lam 0f5b437015
Merge pull request #622 from robinschneider/patch-1 3 years ago
Nelson Chan ac80631bcd Fix: Run clear data at specific time 3 years ago
Nelson Chan 8caf47988c Fix: Allow setting settings type 3 years ago
Robin Schneider 6fe014fa5e
Fixed spelling for german language support 3 years ago
Nelson Chan 6cf2eb036d Fix: Improve settings layout and wording 3 years ago
MrEddX 9d7def93a5
Update bg-BG.js 3 years ago
Nelson Chan dca5a59dbc Feat: Implement data clearing logic & frontend 3 years ago
Nelson Chan 656a4d6270 WIP: Enable background jobs 3 years ago
Louis Lam 6dd0e082b4
Merge pull request #614 from pemassi/patch-1 3 years ago
Kyungyoon Kim 2e95e2016d
Translate to Korean 3 years ago
Louis Lam 4169127143
Update FUNDING.yml 3 years ago
LouisLam cac0a46bac fix error if tls info object is in old format 3 years ago
Bert Verhelst d71d27220b fix(edit-monitor): store headers as JSON 3 years ago
Bert Verhelst fba4f86552 Merge branch 'master' into feature/add-support-for-method-body-and-headers 3 years ago
LouisLam e023ddf1c2 Merge remote-tracking branch 'origin/master' 3 years ago
LouisLam 23a2d33f8c [backup] restore pushToken 3 years ago
Bert Verhelst b8093e909b fix(edit-monitor): fix minification of translations containing { } 3 years ago
Bert Verhelst c3c273f9df fix(edit-monitor): fix regex to allow a single header 3 years ago
Bert Verhelst daab2a05f5 Merge remote-tracking branch 'louislam/master' into feature/add-support-for-method-body-and-headers 3 years ago
LouisLam a15e9077fc [status page] clear cache if it is an important beat 3 years ago
Louis Lam 8431a25a3a
Update README.md 3 years ago
Louis Lam e8cc7ff771
Merge pull request #609 from dpatrongomez/patch-1 3 years ago
新逸Cary 5d617012a3
Merge pull request #5 from louislam/master 3 years ago
Daniel Patrón Gómez d7eac1a413
Update es-ES.js 3 years ago
Louis Lam c589bd836d
[test] change to npm install for pull requests 3 years ago
LouisLam 5ce09953e2 use Segoe UI font for Windows among all languages 3 years ago
LouisLam fc8d1e78b6 [push type] hide upside down mode, apply primary base url 3 years ago
LouisLam 3f26327f95 Merge remote-tracking branch 'origin/master' 3 years ago
Louis Lam efb3f2b19c
Merge pull request #605 from NixNotCastey/promosms 3 years ago
Lukas db791c880a
Don't use then with await. 3 years ago
新逸Cary cdda182311
Merge pull request #4 from louislam/master 3 years ago
LouisLam a1c2a1bc52 [test] auto test for node lts only 3 years ago
Louis Lam 432b156fce
Merge pull request #595 from kvpt/status-page-default-locale 3 years ago
LouisLam 01812cc446 [test] add test for i18n currentLocale 3 years ago
Lukas dfd63386ba
Make PromoSMS actually working 3 years ago
LouisLam 11abc1f1e0 [test] add test for i18n currentLocale 3 years ago
LouisLam 288e87bb3d Merge branch 'master' into status-page-default-locale 3 years ago
Louis Lam 79ee0e1ef4
Update ask-for-help.md 3 years ago
LouisLam 8ae79ab9bf improve #560 3 years ago
Lukas 12b5489eb5 PromoSMS as Notification Provider 3 years ago
LouisLam ddad2dcb4a Merge remote-tracking branch 'origin/master' 3 years ago
LouisLam e96121f69a fix merging mistake 3 years ago
LouisLam 5b4af550fb Merge branch 'master' into DeeJayPee_master 3 years ago
Louis Lam dd183e2ec2
Merge pull request #567 from Empty2k12/feature/matrix-notifications 3 years ago
LouisLam 0fcb310b97 Merge remote-tracking branch 'Empty2k12/feature/matrix-notifications' into feature/matrix-notifications 3 years ago
LouisLam 3a0143ac46 [matrix] use encodeURIComponent to handle the url encode 3 years ago
LouisLam 2ce5c28ed4 Merge branch 'master' into feature/matrix-notifications 3 years ago
Bert Verhelst ec4b7e4064 Merge remote-tracking branch 'louislam/master' into feature/add-support-for-method-body-and-headers 3 years ago
Kevin Petit 5b758a4e98 Better locale default for status page. 3 years ago
LouisLam 7907c07034 Merge remote-tracking branch 'origin/master' 3 years ago
LouisLam adfe640f42 show fewer beat on mobile 3 years ago
Louis Lam 469f7a3e32
Update README.md 3 years ago
LouisLam 9f1e7b0a88 Revert "fix(heartbeat-bar): cleanup css styling and minor syntax issues" 3 years ago
LouisLam cdf81a36d3 fix broken animation caused by #521 3 years ago
LouisLam bf4ac0cf17 fix dockerfile issue on arm 3 years ago
LouisLam c0846124c2 update vite to 1.6.4, since it fixed the issue 3 years ago
LouisLam 3d30ed3d3b update security policy 3 years ago
LouisLam deec15c09e [test] better job name 3 years ago
LouisLam 3423cb5d8e [test] try to auto test Windows and MacOS 3 years ago
LouisLam 20af179a82 [test] try to auto test Windows and MacOS 3 years ago
LouisLam 3c60800eab [test] github action please ok🙏🏻🙏🏻🙏🏻 3 years ago
LouisLam 34586d7b8f [test] github action please ok 3 years ago
LouisLam 2c19aef4dc minor 3 years ago
LouisLam 67a623be18 e2e testing, it's hard 3 years ago
LouisLam e5f6d7f047 slack and rocket.chat use the primary base url 3 years ago
LouisLam b69550f5b9 Improve the test 3 years ago
LouisLam d08a71ab49 Set primary base url in settings page 3 years ago
LouisLam ed67803af8 improve minor style 3 years ago
Louis Lam a6c839709c
Merge pull request #589 from MrEddX/bulgarian 3 years ago
Louis Lam 5eb3c6b194
Merge branch 'master' into bulgarian 3 years ago
LouisLam 8233f3b875 try to standardize the language name list 3 years ago
Bert Verhelst 8be4bf0e16 Merge remote-tracking branch 'louislam/master' into feature/add-support-for-method-body-and-headers 3 years ago
LouisLam cccf393ee5 update zh-HK.js 3 years ago
MrEddX 9f5bf37a96
Update Settings.vue 3 years ago
LouisLam 18e4702375 ignore .env 3 years ago
MrEddX 1eb3f63a82
Update bg-BG.js 3 years ago
Louis Lam 8c63536eb8
Merge pull request #451 from zsxeee/notification_form_i18n 3 years ago
LouisLam 3e1788983e Merge remote-tracking branch 'origin/master' 3 years ago
LouisLam a8badb027d update modded node-sqlite3 to 6.0.0 3 years ago
MrEddX 0c6b434d79
Moved Bulgarian to the Cyrillic family languages 3 years ago
Louis Lam b5bd92ce78
Merge pull request #578 from jtagcat/et5 3 years ago
Louis Lam 3f80cf5e54
Merge pull request #581 from chakflying/patch-2 3 years ago
Bert Verhelst 162ef04c41 Merge branch 'master' into feature/add-support-for-method-body-and-headers 3 years ago
Nelson Chan a87595a849
Fix: Allow underscore in hostname 3 years ago
jtagcat 7626e1f2e4 l10n: update et 3 years ago
zsxeee 7f1edb49bc
Fix i18n 3 years ago
Gero Gerke d184733af9 update text 3 years ago
Gero Gerke 704d63b49f
Merge branch 'master' into feature/matrix-notifications 3 years ago
zsxeee 7002a778f0
Rollback vue-i18n version to 9.1.7 3 years ago
zsxeee 54d2fbcc02
Fix i18n 3 years ago
Gero Gerke fbd4d54812
Update src/components/notifications/Matrix.vue 3 years ago
LouisLam c8706b9aa1 Merge branch 'master' into notification_form_i18n 3 years ago
Louis Lam 54d7830813
Update CONTRIBUTING.md 3 years ago
LouisLam fef26f3d5e Merge remote-tracking branch 'origin/master' 3 years ago
LouisLam cb263f2a08 [test] wait for #language 3 years ago
Louis Lam 52102d72a0
Merge pull request #572 from Brainpitcher/patch-1 3 years ago
LouisLam 2b00e59c7a [test] update 3 years ago
Louis Lam 8ea7a693a1
Merge pull request #499 from Saibamen/fix_pl_i18n 3 years ago
LouisLam 4ded0c073a [test] fix timeout issue 3 years ago
Brainpitcher 7a8b6a03e0
Update ru-RU.js 3 years ago
Gero Gerke 6bebc623f9 UI polish 3 years ago
Gero Gerke 34b86352f2 remove double spaces 3 years ago
Gero Gerke 99e8a33118 escape room characters 3 years ago
Gero Gerke d7cc585101
Update server/notification-providers/matrix.js 3 years ago
Gero Gerke 8c357a04bf
Update src/components/notifications/index.js 3 years ago
DeeJayPee 044c78aa2d Typo \o/ 3 years ago
DeeJayPee 49eaa1a166 Typo fix 3 years ago
DeeJayPee 215cc07907 Rename versions 3 years ago
LouisLam 22227be408 update version in wiki too 3 years ago
Gero Gerke 5decfb9fad Matrix Notifications 3 years ago
DeeJayPee f9d7b99367 Add legacy version back on refactored master branch 3 years ago
DeeJayPee 359fe52c2e Merge branch 'louislam-master' 3 years ago
DeeJayPee bc4db6c692 Merge branch 'master' of https://github.com/louislam/uptime-kuma into louislam-master 3 years ago
DeeJayPee f14a798b2c Fix indentation + typo 3 years ago
Bert Verhelst a0ffa42b42 fix(translations): add translations for method body and headers to dutch 3 years ago
Bert Verhelst 550825927c Merge branch 'master' into feature/add-support-for-method-body-and-headers 3 years ago
LouisLam c1501742f5 test github secret 3 years ago
LouisLam 7c98fe603e test github secret 3 years ago
LouisLam b7ae49c644 update github action 3 years ago
LouisLam edad2caf8e return the correct exit code from jest 3 years ago
LouisLam 2bf6a04f81 fix preparing test 3 years ago
Louis Lam f0670dde20
Update auto-test.yml 3 years ago
Louis Lam 73068763c0
Create auto-test.yml 3 years ago
LouisLam 73bf1216d1 [wip] more test 3 years ago
LouisLam 98436f91b5 Merge remote-tracking branch 'origin/master' 3 years ago
LouisLam 49720c709c improve the test with a single command only "npm test" 3 years ago
Louis Lam 842d359ad3
Update CONTRIBUTING.md 3 years ago
LouisLam e71f5bf314 update dependencies 3 years ago
LouisLam 79e0c9e1f1 setup unit test for setup 3 years ago
LouisLam e6ff957d9d Merge remote-tracking branch 'origin/master' 3 years ago
Louis Lam 865b721b79
Merge pull request #519 from chakflying/improve-certInfo 3 years ago
LouisLam b5d987863d Merge remote-tracking branch 'origin/master' 3 years ago
Louis Lam 911690bea8
Merge pull request #521 from bertyhell/bugfix/heartbeat-bar-cleanup 3 years ago
LouisLam d25603e629 update jest test 3 years ago
LouisLam 259bcf9426 [SMTP] "To Email" is not required if CC/BCC is set. (#461) 3 years ago
Bert Verhelst afeb424dc0 fix(edit-monitor): add translations to en.js 3 years ago
Bert Verhelst 6b44116245 Merge remote-tracking branch 'louislam/master' into feature/add-support-for-method-body-and-headers 3 years ago
Louis Lam aa478af286
Merge pull request #557 from Saibamen/patch-1 3 years ago
Adam Stachowicz 5f8d0faacd
Update CONTRIBUTING.md 3 years ago
Adam Stachowicz 707e05c330 Fix after merge 3 years ago
Adam Stachowicz 4da63c5fb8 Revert `silentTranslationWarn` change 3 years ago
Adam Stachowicz 8ae64843fc run update-language-files 3 years ago
Adam Stachowicz 23e64b8efd Merge branch 'master' into fix_pl_i18n 3 years ago
Louis Lam 8c55a8bf98
Update CONTRIBUTING.md 3 years ago
Louis Lam 387a8919f9
Merge pull request #542 from csabibela/master 3 years ago
Louis Lam a1edc23b1d
Merge pull request #540 from jtagcat/et4 3 years ago
Bert Verhelst 7ee89fab5c
fix(edit-monitor): Make json capitalised 3 years ago
Csábi Béla 980342546e Merge branch 'hu' 3 years ago
Csábi Béla 6b60dc9630 Initial Hungarian translation 3 years ago
Csábi Béla 8d22b43f24 Revert "Initial Hungarian translation" 3 years ago
Csábi Béla dad58341c6 Initial Hungarian translation 3 years ago
jtagcat 275902be38 update estonian translation 3 years ago
jtagcat 38213585f3 update style for et translation 3 years ago
LouisLam 37d1e50ff1 fix data path for test 3 years ago
LouisLam a2a4c70cf5 setup jest-puppeteer 3 years ago
Louis Lam 446fc1af0b
Update CONTRIBUTING.md 3 years ago
LouisLam 51acd107e3 Merge branch 'master' into notification_form_i18n 3 years ago
LouisLam d3517e76c1 change to npm ci 3 years ago
Bert Verhelst 3f0b85e5a8 feat(http-requests): add support for methods, body and headers for http 3 years ago
LouisLam 2625cbe0d2 add script for downloading the dist files from github 3 years ago
LouisLam c93f42794f upload prebuilt dist to github release 3 years ago
Nelson Chan 668fd58af3 Fix: Slightly improve validity styling 3 years ago
Nelson Chan b7568e9caa Fix: Update Certificate Icon 3 years ago
Bert Verhelst 1c2adf8723 fix(monitor-list): increase padding to keep same monitor item height 3 years ago
Bert Verhelst 96129921e9 Merge remote-tracking branch 'origin/master' into bugfix/heartbeat-bar-cleanup 3 years ago
Louis Lam 2b1fe815f9
Merge pull request #522 from bertyhell/bugfix/improve-setup-styles 3 years ago
Louis Lam fcf017d5c7
Merge pull request #523 from bertyhell/bugfix/hide-dashboard-and-settings-when-not-logged-in 3 years ago
Bert Verhelst 843830a38a fix(layout): hide dashboard and settings buttons when not logged in 3 years ago
Bert Verhelst ee830621dd fix(login): fix the same padding issues for the login screen 3 years ago
Bert Verhelst c2a560e2ed fix(setup): increase left padding input fields + avoid clipping 3 years ago
Nelson Chan 13bdfefa9d Feat: Improve Certificaet Info Display 3 years ago
Bert Verhelst 3d6c8b7f05 fix(heartbeat-bar): cleanup css styling and minor syntax issues 3 years ago
LouisLam 2aaed66b38 [docker: debian] reduce the image size dramatically! Compressed size from 240 MB~ to 100 MB~ 3 years ago
LouisLam 7b4c70860c split the base images in order to prevent recompile the base part 3 years ago
LouisLam 6513c3e75f allow update minor version only, to prevent the breaking change in the future like vite 2.6 3 years ago
LouisLam 7fa1cb83af [push type] add ping parameter 3 years ago
Louis Lam 9d079eeec0
Merge pull request #487 from cmandesign/feature/fa-lang-rtl 3 years ago
LouisLam 21dd5ad3dd Merge remote-tracking branch 'origin/master' 3 years ago
LouisLam 3394e1f148 fix undefined callback 3 years ago
Louis Lam ee22406301
Update README.md 3 years ago
LouisLam 8d5eaaf8a7 minor 3 years ago
LouisLam b246c8e0f2 Fix 2fa for iOS Google authenticator (#486) 3 years ago
LouisLam 1ed4ac9494 add Push-based monitoring (#279) 3 years ago
zsxeee 0f2059cde0
Use named slot translation when has multi-slot 3 years ago
zsxeee 138ddf5608
Move attribute `tag` to start of tag 3 years ago
zsxeee dcd68213b1
Merge remote-tracking branch 'upstream/master' into notification_form_i18n 3 years ago
Louis Lam 9e95d568c2
Merge pull request #501 from Saibamen/incident_use_local_timezone 3 years ago
Louis Lam fa3da819f8
Merge pull request #512 from chakflying/reduce-bundle-size 3 years ago
Louis Lam 9e1f1f006b
Merge pull request #511 from chakflying/fix-multiselect-css 3 years ago
Louis Lam 55cb497301
Merge pull request #427 from zsxeee/notification_component 3 years ago
LouisLam 8d8d5987e7 update to 1.7.3 3 years ago
LouisLam 1fa90bffaa freeze vite version to 2.5.* (2.6.* is broken when build the project) 3 years ago
LouisLam 67f221d3c7 update to 1.7.2 3 years ago
Nelson Chan 05f28fecb2 Build: Use async component for status and settings 3 years ago
Nelson Chan ba4a4aaf1c Chore: Move multiselect css to own file 3 years ago
LouisLam 6eceb4c744 Merge remote-tracking branch 'origin/master' 3 years ago
LouisLam 3e4154dfb5 Fix retry interval affected bug 3 years ago
Louis Lam fbc8828ddc
Merge pull request #464 from chakflying/fix-delete-monitor 3 years ago
Soroosh f2c7308c96 Fixed Change Language Direction In Setting Page 3 years ago
LouisLam 3677aa639f update to 1.7.1 3 years ago
LouisLam aaddfa1786 update package-lock.json 3 years ago
LouisLam 6d65d248f4 Merge branch 'bulgarian' 3 years ago
LouisLam 87a4748b40 check back to root user for Debian, until problem has been found (#488) 3 years ago
Louis Lam 182bdf13a7
Merge pull request #500 from Saibamen/remove_timezone_console_log 3 years ago
MrEddX 0f66e5cfc5 Added Bulgarian Language 3 years ago
Louis Lam fe5ae46013
Merge pull request #480 from gaby/init-support 3 years ago
Denis Freund efbadd0737 only allow ip address for hostname when monitor type is steam 3 years ago
Adam Stachowicz f9d633e02b Display created and updated time in local timezone. Fixes #491 3 years ago
Adam Stachowicz 756f317f82 Remove "Skip Timezone" console log from frontend 3 years ago
Adam Stachowicz fa9d26416c `silentTranslationWarn` if not development 3 years ago
Adam Stachowicz 58aa83331e Fix Polish language, add missing `Status Page` i18n 3 years ago
Louis Lam cc9fe26584
Merge pull request #498 from kry008/patch-2 3 years ago
kry008 99818aa370
Update pl.js 3 years ago
Louis Lam 682265fe9c
Merge pull request #481 from DX37/translation-ru 3 years ago
Louis Lam 4406e51ab6
Merge pull request #496 from bertyhell/bugfix/remove-zero-width-spaces 3 years ago
Louis Lam c3d5be5a5e
Merge pull request #493 from iomataani/master 3 years ago
Louis Lam dfe12c99c1
Merge pull request #490 from Minvinea/master 3 years ago
Louis Lam bb69851160
Merge pull request #485 from Lrss/patch-1 3 years ago
Louis Lam 6c9323351d
Merge pull request #484 from xinac721/master 3 years ago
Bert Verhelst 8e9fa20e57 fix(nl-NL): remove ZWSP from translation 3 years ago
Ioma Taani da7c29f4b9 updated translation 3 years ago
Denis Freund b67b4d5afd add steam gameserver for monitoring 3 years ago
Soroosh 9f06d54688 Remove font import and update font-family for lang fa 3 years ago
Soroosh 1448de7b19 Update some translations 3 years ago
Minvinea e545d48583
Update fr-FR.js 3 years ago
Soroosh 47749ca58d Replace some hardcoded with translations 3 years ago
Soroosh 04b7a4a423 Merge Conflict Resolved 3 years ago
Soroosh 56d8f585fd Add postcss-rtlcss and postcss-scss 3 years ago
Soroosh 07c9d78829 Update Farsi translations 3 years ago
Soroosh 15c4a8fb02 Add RTL direction support to styles using postcss & rtlcss 3 years ago
Soroosh f41e95921f Enable localization for pagination 3 years ago
Soroosh 647184e5d1 Update Title to use translation files 3 years ago
Soroosh e60426bdcd Add Localization CSS & Persian Font 3 years ago
Lars Sørensen 8d84d8f891
Update da-DK.js with new strings 3 years ago
新逸Cary 74acc2cea7 Update Simplified Chinese Language(更新简体中文语言) 3 years ago
新逸Cary ba4a4089eb
Merge pull request #3 from louislam/master 3 years ago
Soroosh 251d42f1a6 Add localeDirection method to i18n.js 3 years ago
DX37 122631c91b
fix some ru-RU lines and add new ones 3 years ago
Juan Calderon-Perez 1e12f25c4b Add support for using init to start service 3 years ago
LouisLam 9e10296290 Merge remote-tracking branch 'origin/master' 3 years ago
LouisLam 87e213085f add /status, alias of /status-page (#471) 3 years ago
Louis Lam d9038f1da2
Merge pull request #473 from Ponkhy/german-language 3 years ago
Louis Lam 3fb2d0ce68
Update README.md 3 years ago
Ponkhy 50a33e3b45 Added german translation for the status page 3 years ago
zsxeee f24abac7fc
Merge branch 'master' into notification_component 3 years ago
Nelson Chan 32c9dfbb31 Fix: clear important beats after deleteMonitor 3 years ago
zsxeee 624f632a7a
Apprise status translation key 3 years ago
zsxeee 6e9d12638c
Avoid space ending in translation key 3 years ago
zsxeee 6e55c44773
Chore 3 years ago
zsxeee 601204ae77
Default friendly name i18n and auto increase 3 years ago
zsxeee 8c941b1d56
Add i18n for notification form 3 years ago
zsxeee ad6fcc2f2e
Merge branch 'master' into notification_component 3 years ago
zsxeee ffbc25722d
Move default setting to child component 3 years ago
zsxeee 2fb3c40307
Variable name and key binding 3 years ago
zsxeee 66e40d9fcb
Edit comment and remove unused variable 3 years ago
zsxeee de8b61ef2b
Remove unused imports 3 years ago
zsxeee 534ac4b720
Move Apprise check to child component 3 years ago
zsxeee e9735d239b
Convert notification form to separate component 3 years ago
DeeJayPee 29d0db805d Add legacy octopush (Octopush-DM from 2011 to 2020 accounts) version 3 years ago
  1. 5
      .dockerignore
  2. 17
      .eslintrc.js
  3. 2
      .github/FUNDING.yml
  4. 18
      .github/ISSUE_TEMPLATE/ask-for-help.md
  5. 68
      .github/ISSUE_TEMPLATE/ask-for-help.yaml
  6. 42
      .github/ISSUE_TEMPLATE/bug_report.md
  7. 99
      .github/ISSUE_TEMPLATE/bug_report.yaml
  8. 22
      .github/ISSUE_TEMPLATE/feature_request.md
  9. 59
      .github/ISSUE_TEMPLATE/feature_request.yaml
  10. 28
      .github/PULL_REQUEST_TEMPLATE.md
  11. 35
      .github/workflows/auto-test.yml
  12. 22
      .github/workflows/stale-bot.yml
  13. 2
      .gitignore
  14. 2
      CODE_OF_CONDUCT.md
  15. 211
      CONTRIBUTING.md
  16. 39
      README.md
  17. 24
      SECURITY.md
  18. 11
      babel.config.js
  19. 5
      config/jest-backend.config.js
  20. 5
      config/jest-frontend.config.js
  21. 6
      config/jest-puppeteer.config.js
  22. 11
      config/jest.config.js
  23. 24
      config/vite.config.js
  24. 7
      db/patch-2fa-invalidate-used-token.sql
  25. 13
      db/patch-http-monitor-method-body-and-headers.sql
  26. 7
      db/patch-monitor-push_token.sql
  27. 18
      db/patch-notification_sent_history.sql
  28. 8
      docker/alpine-base.dockerfile
  29. 12
      docker/debian-base.dockerfile
  30. 0
      docker/docker-compose.yml
  31. 52
      docker/dockerfile
  32. 25
      docker/dockerfile-alpine
  33. 33
      dockerfile
  34. 30
      dockerfile-alpine
  35. 6
      ecosystem.config.js
  36. 59
      extra/download-dist.js
  37. 4
      extra/entrypoint.sh
  38. 24
      extra/healthcheck.js
  39. 55
      extra/reset-password.js
  40. 8
      extra/update-language-files/index.js
  41. 47
      extra/update-version.js
  42. 64
      extra/upload-github-release-asset.sh
  43. 2
      index.html
  44. 32
      kubernetes/README.md
  45. 10
      kubernetes/kustomization.yml
  46. 45
      kubernetes/uptime-kuma/deployment.yml
  47. 39
      kubernetes/uptime-kuma/ingressroute.yml
  48. 5
      kubernetes/uptime-kuma/kustomization.yml
  49. 10
      kubernetes/uptime-kuma/pvc.yml
  50. 13
      kubernetes/uptime-kuma/service.yml
  51. 29442
      package-lock.json
  52. 159
      package.json
  53. 32
      server/auth.js
  54. 15
      server/check-version.js
  55. 22
      server/client.js
  56. 7
      server/config.js
  57. 39
      server/database.js
  58. 31
      server/jobs.js
  59. 40
      server/jobs/clear-old-data.js
  60. 39
      server/jobs/util-worker.js
  61. 324
      server/model/monitor.js
  62. 108
      server/notification-providers/aliyun-sms.js
  63. 89
      server/notification-providers/bark.js
  64. 42
      server/notification-providers/clicksendsms.js
  65. 79
      server/notification-providers/dingding.js
  66. 2
      server/notification-providers/discord.js
  67. 83
      server/notification-providers/feishu.js
  68. 2
      server/notification-providers/gotify.js
  69. 2
      server/notification-providers/line.js
  70. 2
      server/notification-providers/lunasea.js
  71. 45
      server/notification-providers/matrix.js
  72. 2
      server/notification-providers/mattermost.js
  73. 64
      server/notification-providers/octopush.js
  74. 41
      server/notification-providers/promosms.js
  75. 2
      server/notification-providers/pushbullet.js
  76. 2
      server/notification-providers/pushover.js
  77. 2
      server/notification-providers/pushy.js
  78. 36
      server/notification-providers/rocket-chat.js
  79. 2
      server/notification-providers/signal.js
  80. 69
      server/notification-providers/slack.js
  81. 59
      server/notification-providers/smtp.js
  82. 2
      server/notification-providers/teams.js
  83. 2
      server/notification-providers/telegram.js
  84. 2
      server/notification-providers/webhook.js
  85. 14
      server/notification.js
  86. 35
      server/ping-lite.js
  87. 31
      server/prometheus.js
  88. 39
      server/rate-limiter.js
  89. 99
      server/routers/api-router.js
  90. 198
      server/server.js
  91. 37
      server/socket-handlers/database-socket-handler.js
  92. 146
      server/util-server.js
  93. 9
      src/App.vue
  94. 46
      src/assets/app.scss
  95. 5
      src/assets/localization.scss
  96. 73
      src/assets/multiselect.scss
  97. 52
      src/components/CertificateInfo.vue
  98. 122
      src/components/CertificateInfoRow.vue
  99. 122
      src/components/CopyableInput.vue
  100. 4
      src/components/HeartbeatBar.vue

5
.dockerignore

@ -1,5 +1,4 @@
/.idea
/dist
/node_modules
/data
/out
@ -19,7 +18,6 @@ README.md
.eslint*
.stylelint*
/.github
package-lock.json
yarn.lock
app.json
CODE_OF_CONDUCT.md
@ -28,7 +26,8 @@ CNAME
install.sh
SECURITY.md
tsconfig.json
.env
/tmp
### .gitignore content (commented rules are duplicated)

17
.eslintrc.js

@ -91,6 +91,23 @@ module.exports = {
"rules": {
"comma-dangle": ["error", "always-multiline"],
}
},
// Override for jest puppeteer
{
"files": [
"**/*.spec.js",
"**/*.spec.jsx"
],
env: {
jest: true,
},
globals: {
page: true,
browser: true,
context: true,
jestPuppeteer: true,
},
}
]
};

2
.github/FUNDING.yml

@ -1,6 +1,6 @@
# These are supported funding model platforms
#github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
github: louislam # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
#patreon: # Replace with a single Patreon username
open_collective: uptime-kuma # Replace with a single Open Collective username
#ko_fi: # Replace with a single Ko-fi username

18
.github/ISSUE_TEMPLATE/ask-for-help.md

@ -1,18 +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=
**Info**
Uptime Kuma Version:
Using Docker?: Yes/No
Docker Version:
Node.js Version (Without Docker only):
OS:
Browser:

68
.github/ISSUE_TEMPLATE/ask-for-help.yaml

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

42
.github/ISSUE_TEMPLATE/bug_report.md

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

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

22
.github/ISSUE_TEMPLATE/feature_request.md

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

59
.github/ISSUE_TEMPLATE/feature_request.yaml

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

@ -0,0 +1,28 @@
# Description
Fixes #(issue)
## Type of change
Please delete options that are not relevant.
- Bug fix (non-breaking change which fixes an issue)
- User Interface
- 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 test 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.

35
.github/workflows/auto-test.yml

@ -0,0 +1,35 @@
# This workflow will do a clean install of node dependencies, cache/restore them, build the source code and run tests across different versions of node
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
name: Auto Test
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
auto-test:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [macos-latest, ubuntu-latest, windows-latest]
node-version: [14.x, 16.x, 17.x]
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
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 run install-legacy
- run: npm run build
- run: npm test
env:
HEADLESS_TEST: 1
JUST_FOR_TEST: ${{ secrets.JUST_FOR_TEST }}

22
.github/workflows/stale-bot.yml

@ -0,0 +1,22 @@
name: 'Automatically close stale issues and PRs'
on:
schedule:
- cron: '0 0 * * *'
#Run once a day at midnight
jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v4
with:
stale-issue-message: 'We are clearing up our old issues and your ticket has been open for 6 months with no activity. Remove stale label or comment or this will be closed in 7 days.'
stale-pr-message: 'We are clearing up our old Pull Requests and yours has been open for 6 months with no activity. Remove stale label or comment or this will be closed in 7 days.'
close-issue-message: 'This issue was closed because it has been stalled for 7 days with no activity.'
close-pr-message: 'This PR was closed because it has been stalled for 7 days with no activity.'
days-before-stale: 180
days-before-close: 7
exempt-issue-labels: 'News,Medium,High,discussion,bug,doc,'
exempt-pr-labels: 'awaiting-approval,work-in-progress,enhancement,'
exempt-issue-assignees: 'louislam'
exempt-pr-assignees: 'louislam'

2
.gitignore

@ -11,3 +11,5 @@ dist-ssr
/private
/out
/tmp
.env

2
CODE_OF_CONDUCT.md

@ -60,7 +60,7 @@ representative at an online or offline event.
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
louis@uptimekuma.louislam.net.
uptime@kuma.pet.
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the

211
CONTRIBUTING.md

@ -4,54 +4,103 @@ First of all, thank you everyone who made pull requests for Uptime Kuma, I never
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 frontend code build into "dist" directory. The server uses "dist" as root. This is how production is working.
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.
# Can I create a pull request for Uptime Kuma?
## Key Technical Skills
Generally, if the pull request is working fine and it do not affect any existing logic, workflow and perfomance, I will merge to the master branch once it is tested.
- Node.js (You should know what are promise, async/await and arrow function etc.)
- Socket.io
- SCSS
- Vue.js
- Bootstrap
- SQLite
If you are not sure, feel free to create an empty pull request draft first.
## Directories
## Pull Request Examples
- data (App data)
- dist (Frontend build)
- extra (Extra useful scripts)
- public (Frontend resources for dev only)
- server (Server source code)
- src (Frontend source code)
- test (unit test)
### ✅ High - Medium Priority
## 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.
If you are not sure whether I will accept your pull request, feel free to create an empty pull request draft first.
### Recommended Pull Request Guideline
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"
### Pull Request Examples
Here are some example situations in the past.
#### ✅ High - Medium Priority
Easy to review, no breaking change and not touching the existing code
- Add a new notification
- Add a chart
- Fix a bug
- Translations
- Add a independent new feature
### *️⃣ Requires one more reviewer
#### *️⃣ Requires one more reviewer
I do not have such knowledge to test it.
- Add k8s supports
### *️⃣ Low Priority
#### ⚠ Low Priority - Harsh Mode
Some pull requests are required to modifiy the core. To be honest, I do not want anyone to try to do that, because it would spend a lot of your time. I will review your pull request harshly. Also you may need to write a lot of unit tests to ensure that there is no breaking change.
- Touch large parts of code of any very important features
- Touch monitoring logic
- Drop a table or drop a column for any reason
- Touch the entry point of Docker or Node.js
- Modifiy auth
#### *️⃣ Low Priority
It changed my current workflow and require further studies.
- Change my release approach
### ❌ Won't Merge
#### ❌ Won't Merge
- Any breaking changes
- Duplicated pull request
- Buggy
- Existing logic is completely modified or deleted
- A function that is completely out of scope
# Project Styles
## Project Styles
I personally do not like something need to learn so much and need to config so much before you can finally start the app.
For example, recently, because I am not a python expert, I spent a 2 hours to resolve all problems in order to install and use the Apprise cli. Apprise requires so many hidden requirements, I have to figure out myself how to solve the problems by Google search for my OS. That is painful. I do not want Uptime Kuma to be like this way, so:
- Easy to install for non-Docker users, no native build dependency is needed (at least for x86_64), no extra config, no extra effort to get it run
- Single container for Docker users, no very complex docker-composer file. Just map the volume and expose the port, then good to go
- All settings in frontend.
- Single container for Docker users, no very complex docker-compose file. Just map the volume and expose the port, then good to go
- Settings should be configurable in the frontend. Env var is not encouraged.
- Easy to use
# Coding Styles
## Coding Styles
- 4 spaces indentation
- Follow `.editorconfig`
- Follow ESLint
@ -61,26 +110,20 @@ For example, recently, because I am not a python expert, I spent a 2 hours to re
- SQLite: underscore_type
- CSS/SCSS: dash-type
# Tools
## Tools
- Node.js >= 14
- Git
- IDE that supports EditorConfig and ESLint (I am using Intellji Idea)
- A SQLite tool (I am using SQLite Expert Personal)
- IDE that supports ESLint and EditorConfig (I am using Intellji Idea)
- A SQLite tool (SQLite Expert Personal is suggested)
# Install dependencies
## Install dependencies
```bash
npm install --dev
npm ci
```
For npm@7, you need --legacy-peer-deps
```bash
npm install --legacy-peer-deps --dev
```
# Backend Dev
## How to start the Backend Dev Server
(2021-09-23 Update)
@ -90,55 +133,123 @@ npm run start-server-dev
It binds to `0.0.0.0:3001` by default.
## Backend Details
### Backend Details
It is mainly a socket.io app + express.js.
express.js is just used for serving the frontend built files (index.html, .js and .css etc.)
# Frontend Dev
- model/ (Object model, auto mapping to the database table name)
- modules/ (Modified 3rd-party modules)
- notification-providers/ (indivdual notification logic)
- routers/ (Express Routers)
- scoket-handler (Socket.io Handlers)
- server.js (Server main logic)
Start frontend dev server. Hot-reload enabled in this way. It binds to `0.0.0.0:3000` by default.
## How to start the Frontend Dev Server
```bash
npm run dev
```
1. Set the env var `NODE_ENV` to "development".
2. Start the frontend dev server by the following command.
```bash
npm run dev
```
PS: You can ignore those scss warnings, those warnings are from Bootstrap that I cannot fix.
It binds to `0.0.0.0:3000` by default.
You can use Vue.js devtools Chrome extension for debugging.
After the frontend server started. It cannot connect to the websocket server even you have started the server. You need to tell the frontend that is a dev env by running this in DevTool console and refresh:
### Build the frontend
```javascript
localStorage.dev = "dev";
```bash
npm run build
```
So that the frontend will try to connect websocket server in 3001.
### Frontend Details
Alternately, you can specific `NODE_ENV` to "development".
Uptime Kuma Frontend is a single page application (SPA). Most paths are handled by Vue Router.
## Build the frontend
The router is in `src/router.js`
As you can see, most data in frontend is stored in root level, even though you changed the current router to any other pages.
The data and socket logic are in `src/mixins/socket.js`.
## Database Migration
1. Create `patch-{name}.sql` in `./db/`
2. Add your patch filename in the `patchList` list in `./server/database.js`
## Unit Test
It is an end-to-end testing. It is using Jest and Puppeteer.
```bash
npm run build
npm test
```
## Frontend Details
By default, the Chromium window will be shown up during the test. Specifying `HEADLESS_TEST=1` for terminal environments.
Uptime Kuma Frontend is a single page application (SPA). Most paths are handled by Vue Router.
## Update Dependencies
The router is in `src/router.js`
Install `ncu`
https://github.com/raineorshine/npm-check-updates
As you can see, most data in frontend is stored in root level, even though you changed the current router to any other pages.
```bash
ncu -u -t patch
npm install
```
The data and socket logic are in `src/mixins/socket.js`.
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
# Database Migration
1. Create `patch{num}.sql` in `./db/`
2. Update `latestVersion` in `./server/database.js`
## Wiki
# Unit Test
Since there is no way to make a pull request to wiki's repo, I have setup 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
1. Make sure the repo is cleared
1. `npm run update-version 1.X.X`
1. `npm run build`
1. `npm run build-docker`
1. `git push`
1. Publish the release note as 1.X.X
1. `npm run upload-artifacts`
1. 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 install with Node.js
### Release Wiki
#### Setup Repo
```
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
```
git pull
git push production master
```
Yes, no unit test for now. I know it is very important, but at the same time my spare time is very limited. I want to implement my ideas first. I will go back to this in some points.

39
README.md

@ -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://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="" />
@ -8,7 +9,7 @@
It is a self-hosted monitoring tool like "Uptime Robot".
<img src="https://louislam.net/uptimekuma/1.jpg" width="512" alt="" />
<img src="https://uptime.kuma.pet/img/dark.jpg" width="700" alt="" />
## 🥔 Live Demo
@ -16,17 +17,20 @@ Try it!
https://demo.uptime.kuma.pet
It is a 5 minutes live demo, all data will be deleted after that. The server is located at Tokyo, if you live far away from here, it may affact your experience. I suggest that you should install to try it.
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.
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.
* Monitoring uptime for HTTP(s) / TCP / Ping / DNS Record / Push.
* Fancy, Reactive, Fast UI/UX.
* Notifications via Telegram, Discord, Gotify, Slack, Pushover, Email (SMTP), and [70+ notification services, click here for the full list](https://github.com/louislam/uptime-kuma/issues/284).
* 20 seconds interval.
* 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.
* [Multi Languages](https://github.com/louislam/uptime-kuma/tree/master/src/languages)
* Simple Status Page
* Ping Chart
* Certificate Info
## 🔧 How to Install
@ -37,13 +41,16 @@ 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
```
Browse to http://localhost:3001 after started.
Browse to http://localhost:3001 after starting.
### 💪🏻 Without Docker
### 💪🏻 Non-Docker
Required Tools: Node.js >= 14, git and pm2.
```bash
# Update your npm to the latest version
npm install npm -g
git clone https://github.com/louislam/uptime-kuma.git
cd uptime-kuma
npm run setup
@ -52,11 +59,11 @@ npm run setup
node server/server.js
# (Recommended) Option 2. Run in background using PM2
# Install PM2 if you don't have: npm install pm2 -g
# Install PM2 if you don't have it: npm install pm2 -g
pm2 start server/server.js --name uptime-kuma
```
Browse to http://localhost:3001 after started.
Browse to http://localhost:3001 after starting.
### Advanced Installation
@ -82,9 +89,13 @@ https://github.com/louislam/uptime-kuma/projects/1
## 🖼 More Screenshots
Dark Mode:
Light Mode:
<img src="https://uptime.kuma.pet/img/light.jpg" width="512" alt="" />
Status Page:
<img src="https://user-images.githubusercontent.com/1336778/128710166-908f8d88-9256-43f3-9c49-bfc2c56011d2.png" width="400" alt="" />
<img src="https://user-images.githubusercontent.com/1336778/134628766-a3fe0981-0926-4285-ab46-891a21c3e4cb.png" width="512" alt="" />
Settings Page:
@ -108,11 +119,13 @@ If you love this project, please consider giving me a ⭐.
## 🗣️ Discussion
### Issues Page
You can discuss or ask for help in [Issues](https://github.com/louislam/uptime-kuma/issues).
### Subreddit
My Reddit account: louislamlam
You can mention me if you ask question on Reddit.
You can mention me if you ask a question on Reddit.
https://www.reddit.com/r/UptimeKuma/
## Contribute

24
SECURITY.md

@ -1,15 +1,31 @@
# 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
currently being supported with security updates.
### Uptime Kuma Versions
| Version | Supported |
| ------- | ------------------ |
| 1.x.x | :white_check_mark: |
| 1.9.X | :white_check_mark: |
| <= 1.8.X | ❌ |
## Reporting a Vulnerability
Please report security issues to uptime@kuma.pet.
### Upgradable Docker Tags
Do not use the issue tracker or discuss it in the public as it will cause more damage.
| Tag | Supported |
| ------- | ------------------ |
| 1 | :white_check_mark: |
| 1-debian | :white_check_mark: |
| 1-alpine | :white_check_mark: |
| latest | :white_check_mark: |
| debian | :white_check_mark: |
| alpine | :white_check_mark: |
| All other tags | ❌ |

11
babel.config.js

@ -0,0 +1,11 @@
const config = {};
if (process.env.TEST_FRONTEND) {
config.presets = ["@babel/preset-env"];
}
if (process.env.TEST_BACKEND) {
config.plugins = ["babel-plugin-rewire"];
}
module.exports = config;

5
config/jest-backend.config.js

@ -0,0 +1,5 @@
module.exports = {
"rootDir": "..",
"testRegex": "./test/backend.spec.js",
};

5
config/jest-frontend.config.js

@ -0,0 +1,5 @@
module.exports = {
"rootDir": "..",
"testRegex": "./test/frontend.spec.js",
};

6
config/jest-puppeteer.config.js

@ -0,0 +1,6 @@
module.exports = {
"launch": {
"headless": process.env.HEADLESS_TEST || false,
"userDataDir": "./data/test-chrome-profile",
}
};

11
config/jest.config.js

@ -0,0 +1,11 @@
module.exports = {
"verbose": true,
"preset": "jest-puppeteer",
"globals": {
"__DEV__": true
},
"testRegex": "./test/e2e.spec.js",
"rootDir": "..",
"testTimeout": 30000,
};

24
config/vite.config.js

@ -0,0 +1,24 @@
import legacy from "@vitejs/plugin-legacy";
import vue from "@vitejs/plugin-vue";
import { defineConfig } from "vite";
const postCssScss = require("postcss-scss");
const postcssRTLCSS = require("postcss-rtlcss");
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
vue(),
legacy({
targets: ["ie > 11"],
additionalLegacyPolyfills: ["regenerator-runtime/runtime"]
})
],
css: {
postcss: {
"parser": postCssScss,
"map": false,
"plugins": [postcssRTLCSS]
}
},
});

7
db/patch-2fa-invalidate-used-token.sql

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

13
db/patch-http-monitor-method-body-and-headers.sql

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

7
db/patch-monitor-push_token.sql

@ -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 monitor
ADD push_token VARCHAR(20) DEFAULT NULL;
COMMIT;

18
db/patch-notification_sent_history.sql

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

8
docker/alpine-base.dockerfile

@ -0,0 +1,8 @@
# DON'T UPDATE TO alpine3.13, 1.14, see #41.
FROM node:14-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 && \
rm -rf /root/.cache

12
docker/debian-base.dockerfile

@ -0,0 +1,12 @@
# 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
WORKDIR /app
# 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!
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 && \
rm -rf /var/lib/apt/lists/*

0
docker-compose.yml → docker/docker-compose.yml

52
docker/dockerfile

@ -0,0 +1,52 @@
FROM louislam/uptime-kuma:base-debian AS build
WORKDIR /app
ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=1
COPY . .
RUN npm ci --production && \
chmod +x /app/extra/entrypoint.sh
FROM louislam/uptime-kuma:base-debian AS release
WORKDIR /app
# Copy app files from build layer
COPY --from=build /app /app
EXPOSE 3001
VOLUME ["/app/data"]
HEALTHCHECK --interval=60s --timeout=30s --start-period=180s --retries=5 CMD node extra/healthcheck.js
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=1.9.1
ARG GITHUB_TOKEN
ARG TARGETARCH
ARG PLATFORM=debian
ARG FILE=$PLATFORM-$TARGETARCH-$VERSION.tar.gz
ARG DIST=dist.tar.gz
RUN chmod +x /app/extra/upload-github-release-asset.sh
# Full Build
# RUN tar -zcvf $FILE app
# RUN /app/extra/upload-github-release-asset.sh github_api_token=$GITHUB_TOKEN owner=louislam repo=uptime-kuma tag=$VERSION filename=$FILE
# Dist only
RUN cd /app && tar -zcvf $DIST dist
RUN /app/extra/upload-github-release-asset.sh github_api_token=$GITHUB_TOKEN owner=louislam repo=uptime-kuma tag=$VERSION filename=/app/$DIST

25
docker/dockerfile-alpine

@ -0,0 +1,25 @@
FROM louislam/uptime-kuma:base-alpine AS build
WORKDIR /app
ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=1
COPY . .
RUN npm ci --production && \
chmod +x /app/extra/entrypoint.sh
FROM louislam/uptime-kuma:base-alpine AS release
WORKDIR /app
# Copy app files from build layer
COPY --from=build /app /app
EXPOSE 3001
VOLUME ["/app/data"]
HEALTHCHECK --interval=60s --timeout=30s --start-period=180s --retries=5 CMD node extra/healthcheck.js
ENTRYPOINT ["/usr/bin/dumb-init", "--", "extra/entrypoint.sh"]
CMD ["node", "server/server.js"]
FROM release AS nightly
RUN npm run mark-as-nightly

33
dockerfile

@ -1,33 +0,0 @@
# DON'T UPDATE TO node:14-bullseye-slim, see #372.
# If the image changed, the second stage image should be changed too
FROM node:14-buster-slim AS build
WORKDIR /app
COPY . .
RUN npm install --legacy-peer-deps && \
npm run build && \
npm prune --production && \
chmod +x /app/extra/entrypoint.sh
FROM node:14-buster-slim AS release
WORKDIR /app
# Install Apprise, add sqlite3 cli for debugging in the future, iputils-ping for ping, util-linux for setpriv
RUN apt update && \
apt --yes install python3 python3-pip python3-cryptography python3-six python3-yaml python3-click python3-markdown python3-requests python3-requests-oauthlib \
sqlite3 iputils-ping util-linux && \
pip3 --no-cache-dir install apprise && \
rm -rf /var/lib/apt/lists/*
# Copy app files from build layer
COPY --from=build /app /app
EXPOSE 3001
VOLUME ["/app/data"]
HEALTHCHECK --interval=60s --timeout=30s --start-period=180s --retries=5 CMD node extra/healthcheck.js
ENTRYPOINT ["extra/entrypoint.sh"]
CMD ["node", "server/server.js"]
FROM release AS nightly
RUN npm run mark-as-nightly

30
dockerfile-alpine

@ -1,30 +0,0 @@
# DON'T UPDATE TO alpine3.13, 1.14, see #41.
FROM node:14-alpine3.12 AS build
WORKDIR /app
COPY . .
RUN npm install --legacy-peer-deps && \
npm run build && \
npm prune --production && \
chmod +x /app/extra/entrypoint.sh
FROM node:14-alpine3.12 AS release
WORKDIR /app
# Install apprise, iputils for non-root ping, setpriv
RUN apk add --no-cache iputils setpriv python3 py3-cryptography py3-pip py3-six py3-yaml py3-click py3-markdown py3-requests py3-requests-oauthlib && \
pip3 --no-cache-dir install apprise && \
rm -rf /root/.cache
# Copy app files from build layer
COPY --from=build /app /app
EXPOSE 3001
VOLUME ["/app/data"]
HEALTHCHECK --interval=60s --timeout=30s --start-period=180s --retries=5 CMD node extra/healthcheck.js
ENTRYPOINT ["extra/entrypoint.sh"]
CMD ["node", "server/server.js"]
FROM release AS nightly
RUN npm run mark-as-nightly

6
ecosystem.config.js

@ -0,0 +1,6 @@
module.exports = {
apps: [{
name: "uptime-kuma",
script: "./server/server.js",
}]
}

59
extra/download-dist.js

@ -0,0 +1,59 @@
console.log("Downloading dist");
const https = require("https");
const tar = require("tar");
const packageJSON = require("../package.json");
const fs = require("fs");
const version = packageJSON.version;
const filename = "dist.tar.gz";
const url = `https://github.com/louislam/uptime-kuma/releases/download/${version}/${filename}`;
download(url);
function download(url) {
console.log(url);
https.get(url, (response) => {
if (response.statusCode === 200) {
console.log("Extracting dist...");
if (fs.existsSync("./dist")) {
if (fs.existsSync("./dist-backup")) {
fs.rmdirSync("./dist-backup", {
recursive: true
});
}
fs.renameSync("./dist", "./dist-backup");
}
const tarStream = tar.x({
cwd: "./",
});
tarStream.on("close", () => {
if (fs.existsSync("./dist-backup")) {
fs.rmdirSync("./dist-backup", {
recursive: true
});
}
console.log("Done");
});
tarStream.on("error", () => {
if (fs.existsSync("./dist-backup")) {
fs.renameSync("./dist-backup", "./dist");
}
console.error("Error from tarStream");
});
response.pipe(tarStream);
} else if (response.statusCode === 302) {
download(response.headers.location);
} else {
console.log("dist not found");
}
});
}

4
extra/entrypoint.sh

@ -2,8 +2,8 @@
# set -e Exit the script if an error happens
set -e
PUID=${PUID=1000}
PGID=${PGID=1000}
PUID=${PUID=0}
PGID=${PGID=0}
files_ownership () {
# -h Changes the ownership of an encountered symbolic link and not that of the file or directory pointed to by the symbolic link.

24
extra/healthcheck.js

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

55
extra/reset-password.js

@ -12,50 +12,59 @@ const rl = readline.createInterface({
output: process.stdout
});
(async () => {
const main = async () => {
Database.init(args);
await Database.connect();
try {
const user = await R.findOne("user");
if (! user) {
throw new Error("user not found, have you installed?");
}
// 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);
console.log("Found user: " + user.username);
while (true) {
let password = await question("New Password: ");
let confirmPassword = await question("Confirm New Password: ");
while (true) {
let password = await question("New Password: ");
let confirmPassword = await question("Confirm New Password: ");
if (password === confirmPassword) {
await user.resetPassword(password);
if (password === confirmPassword) {
await user.resetPassword(password);
// Reset all sessions by reset jwt secret
await initJWTSecret();
// Reset all sessions by reset jwt secret
await initJWTSecret();
rl.close();
break;
} else {
console.log("Passwords do not match, please try again.");
break;
} else {
console.log("Passwords do not match, please try again.");
}
}
console.log("Password reset successfully.");
}
console.log("Password reset successfully.");
} catch (e) {
console.error("Error: " + e.message);
}
await Database.close();
rl.close();
console.log("Finished. You should restart the Uptime Kuma server.")
})();
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,
};

8
extra/update-language-files/index.js

@ -26,10 +26,12 @@ const copyRecursiveSync = function (src, dest) {
}
};
console.log("Arguments:", process.argv)
console.log("Arguments:", process.argv);
const baseLangCode = process.argv[2] || "en";
console.log("Base Lang: " + baseLangCode);
fs.rmdirSync("./languages", { recursive: true });
if (fs.existsSync("./languages")) {
fs.rmdirSync("./languages", { recursive: true });
}
copyRecursiveSync("../../src/languages", "./languages");
const en = (await import("./languages/en.js")).default;
@ -39,7 +41,7 @@ console.log("Files:", files);
for (const file of files) {
if (!file.endsWith(".js")) {
console.log("Skipping " + file)
console.log("Skipping " + file);
continue;
}

47
extra/update-version.js

@ -19,6 +19,7 @@ if (! newVersion) {
const exists = tagExists(newVersion);
if (! exists) {
// Process package.json
pkg.version = newVersion;
pkg.scripts.setup = pkg.scripts.setup.replaceAll(oldVersion, newVersion);
@ -29,8 +30,11 @@ if (! exists) {
commit(newVersion);
tag(newVersion);
updateWiki(oldVersion, newVersion);
} else {
console.log("version exists")
console.log("version exists");
}
function commit(version) {
@ -38,16 +42,16 @@ function commit(version) {
let res = child_process.spawnSync("git", ["commit", "-m", msg, "-a"]);
let stdout = res.stdout.toString().trim();
console.log(stdout)
console.log(stdout);
if (stdout.includes("no changes added to commit")) {
throw new Error("commit error")
throw new Error("commit error");
}
}
function tag(version) {
let res = child_process.spawnSync("git", ["tag", version]);
console.log(res.stdout.toString().trim())
console.log(res.stdout.toString().trim());
}
function tagExists(version) {
@ -59,3 +63,38 @@ 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,
});
}
}

64
extra/upload-github-release-asset.sh

@ -0,0 +1,64 @@
#!/usr/bin/env bash
#
# Author: Stefan Buck
# License: MIT
# https://gist.github.com/stefanbuck/ce788fee19ab6eb0b4447a85fc99f447
#
#
# This script accepts the following parameters:
#
# * owner
# * repo
# * tag
# * filename
# * github_api_token
#
# Script to upload a release asset using the GitHub API v3.
#
# Example:
#
# upload-github-release-asset.sh github_api_token=TOKEN owner=stefanbuck repo=playground tag=v0.1.0 filename=./build.zip
#
# Check dependencies.
set -e
xargs=$(which gxargs || which xargs)
# Validate settings.
[ "$TRACE" ] && set -x
CONFIG=$@
for line in $CONFIG; do
eval "$line"
done
# Define variables.
GH_API="https://api.github.com"
GH_REPO="$GH_API/repos/$owner/$repo"
GH_TAGS="$GH_REPO/releases/tags/$tag"
AUTH="Authorization: token $github_api_token"
WGET_ARGS="--content-disposition --auth-no-challenge --no-cookie"
CURL_ARGS="-LJO#"
if [[ "$tag" == 'LATEST' ]]; then
GH_TAGS="$GH_REPO/releases/latest"
fi
# Validate token.
curl -o /dev/null -sH "$AUTH" $GH_REPO || { echo "Error: Invalid repo, token or network issue!"; exit 1; }
# Read asset tags.
response=$(curl -sH "$AUTH" $GH_TAGS)
# Get ID of the asset based on given filename.
eval $(echo "$response" | grep -m 1 "id.:" | grep -w id | tr : = | tr -cd '[[:alnum:]]=')
[ "$id" ] || { echo "Error: Failed to get release id for tag: $tag"; echo "$response" | awk 'length($0)<100' >&2; exit 1; }
# Upload asset
echo "Uploading asset... "
# Construct url
GH_ASSET="https://uploads.github.com/repos/$owner/$repo/releases/$id/assets?name=$(basename $filename)"
curl "$GITHUB_OAUTH_BASIC" --data-binary @"$filename" -H "Authorization: token $github_api_token" -H "Content-Type: application/octet-stream" $GH_ASSET

2
index.html

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

32
kubernetes/README.md

@ -1,32 +0,0 @@
# Uptime-Kuma K8s Deployment
⚠ Warning: K8s deployment is provided by contributors. I have no experience with K8s and I can't fix error in the future. I only test Docker and Node.js. Use at your own risk.
## How does it work?
Kustomize is a tool which builds a complete deployment file for all config elements.
You can edit the files in the ```uptime-kuma``` folder except the ```kustomization.yml``` until you know what you're doing.
If you want to choose another namespace you can edit the ```kustomization.yml``` in the ```kubernetes```-Folder and change the ```namespace: uptime-kuma``` to something you like.
It creates a certificate with the specified Issuer and creates the Ingress for the Uptime-Kuma ClusterIP-Service.
## What do I have to edit?
You have to edit the ```ingressroute.yml``` to your needs.
This ingressroute.yml is for the [nginx-ingress-controller](https://kubernetes.github.io/ingress-nginx/) in combination with the [cert-manager](https://cert-manager.io/).
- Host
- Secrets and secret names
- (Cluster)Issuer (optional)
- The Version in the Deployment-File
- Update:
- Change to newer version and run the above commands, it will update the pods one after another
## How To use
- Install [kustomize](https://kubectl.docs.kubernetes.io/installation/kustomize/)
- Edit files mentioned above to your needs
- Run ```kustomize build > apply.yml```
- Run ```kubectl apply -f apply.yml```
Now you should see some k8s magic and Uptime-Kuma should be available at the specified address.

10
kubernetes/kustomization.yml

@ -1,10 +0,0 @@
namespace: uptime-kuma
namePrefix: uptime-kuma-
commonLabels:
app: uptime-kuma
bases:
- uptime-kuma

45
kubernetes/uptime-kuma/deployment.yml

@ -1,45 +0,0 @@
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
component: uptime-kuma
name: deployment
spec:
selector:
matchLabels:
component: uptime-kuma
replicas: 1
strategy:
type: Recreate
template:
metadata:
labels:
component: uptime-kuma
spec:
containers:
- name: app
image: louislam/uptime-kuma:1
ports:
- containerPort: 3001
volumeMounts:
- mountPath: /app/data
name: storage
livenessProbe:
exec:
command:
- node
- extra/healthcheck.js
initialDelaySeconds: 180
periodSeconds: 60
timeoutSeconds: 30
readinessProbe:
httpGet:
path: /
port: 3001
scheme: HTTP
volumes:
- name: storage
persistentVolumeClaim:
claimName: pvc

39
kubernetes/uptime-kuma/ingressroute.yml

@ -1,39 +0,0 @@
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
kubernetes.io/ingress.class: nginx
cert-manager.io/cluster-issuer: letsencrypt-prod
nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
nginx.ingress.kubernetes.io/server-snippets: |
location / {
proxy_set_header Upgrade $http_upgrade;
proxy_http_version 1.1;
proxy_set_header X-Forwarded-Host $http_host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header Host $host;
proxy_set_header Connection "upgrade";
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Upgrade $http_upgrade;
proxy_cache_bypass $http_upgrade;
}
name: ingress
spec:
tls:
- hosts:
- example.com
secretName: example-com-tls
rules:
- host: example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: service
port:
number: 3001

5
kubernetes/uptime-kuma/kustomization.yml

@ -1,5 +0,0 @@
resources:
- deployment.yml
- service.yml
- ingressroute.yml
- pvc.yml

10
kubernetes/uptime-kuma/pvc.yml

@ -1,10 +0,0 @@
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pvc
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 4Gi

13
kubernetes/uptime-kuma/service.yml

@ -1,13 +0,0 @@
apiVersion: v1
kind: Service
metadata:
name: service
spec:
selector:
component: uptime-kuma
type: ClusterIP
ports:
- name: http
port: 3001
targetPort: 3001
protocol: TCP

29442
package-lock.json

File diff suppressed because it is too large

159
package.json

@ -1,6 +1,6 @@
{
"name": "uptime-kuma",
"version": "1.7.0",
"version": "1.10.2",
"license": "MIT",
"repository": {
"type": "git",
@ -15,20 +15,29 @@
"lint:js": "eslint --ext \".js,.vue\" --ignore-path .gitignore .",
"lint:style": "stylelint \"**/*.{vue,css,scss}\" --ignore-path .gitignore",
"lint": "npm run lint:js && npm run lint:style",
"dev": "vite --host",
"dev": "vite --host --config ./config/vite.config.js",
"start": "npm run start-server",
"start-server": "node server/server.js",
"start-server-dev": "cross-env NODE_ENV=development node server/server.js",
"build": "vite build",
"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-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",
"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-alpine": "docker buildx build -f dockerfile-alpine --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:alpine -t louislam/uptime-kuma:1-alpine -t louislam/uptime-kuma:1.7.0-alpine --target release . --push",
"build-docker-debian": "docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma -t louislam/uptime-kuma:1 -t louislam/uptime-kuma:1.7.0 -t louislam/uptime-kuma:debian -t louislam/uptime-kuma:1-debian -t louislam/uptime-kuma:1.7.0-debian --target release . --push",
"build-docker-nightly": "docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:nightly --target nightly . --push",
"build-docker-nightly-alpine": "docker buildx build -f 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 --platform linux/amd64 -t louislam/uptime-kuma:nightly-amd64 --target nightly . --push --progress plain",
"setup": "git checkout 1.7.0 && npm install --legacy-peer-deps && node node_modules/esbuild/install.js && npm run build && npm prune",
"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.10.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.10.2 -t louislam/uptime-kuma:debian -t louislam/uptime-kuma:1-debian -t louislam/uptime-kuma:1.10.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-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.10.2 && 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",
@ -43,66 +52,80 @@
"update-language-files": "cd extra/update-language-files && node index.js && eslint ../../src/languages/**.js --fix"
},
"dependencies": {
"@fortawesome/fontawesome-svg-core": "^1.2.36",
"@fortawesome/free-regular-svg-icons": "^5.15.4",
"@fortawesome/free-solid-svg-icons": "^5.15.4",
"@fortawesome/vue-fontawesome": "^3.0.0-4",
"@louislam/sqlite3": "^5.0.6",
"@popperjs/core": "^2.10.1",
"args-parser": "^1.3.0",
"axios": "^0.21.4",
"bcryptjs": "^2.4.3",
"bootstrap": "^5.1.1",
"chart.js": "^3.5.1",
"chartjs-adapter-dayjs": "^1.0.0",
"command-exists": "^1.2.9",
"compare-versions": "^3.6.0",
"dayjs": "^1.10.7",
"express": "^4.17.1",
"express-basic-auth": "^1.2.0",
"form-data": "^4.0.0",
"http-graceful-shutdown": "^3.1.4",
"jsonwebtoken": "^8.5.1",
"nodemailer": "^6.6.5",
"notp": "^2.0.3",
"password-hash": "^1.2.2",
"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",
"tcp-ping": "^0.1.1",
"thirty-two": "^1.0.2",
"timezones-list": "^3.0.1",
"v-pagination-3": "^0.1.6",
"@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",
"@popperjs/core": "~2.10.2",
"args-parser": "~1.3.0",
"axios": "~0.21.4",
"bcryptjs": "~2.4.3",
"bootstrap": "5.1.3",
"bree": "~6.3.1",
"chardet": "^1.3.0",
"chart.js": "~3.6.0",
"chartjs-adapter-dayjs": "~1.0.0",
"check-password-strength": "^2.0.3",
"command-exists": "~1.2.9",
"compare-versions": "~3.6.0",
"dayjs": "~1.10.7",
"express": "~4.17.1",
"express-basic-auth": "~1.2.0",
"form-data": "~4.0.0",
"http-graceful-shutdown": "~3.1.4",
"iconv-lite": "^0.6.3",
"jsonwebtoken": "~8.5.1",
"jwt-decode": "^3.1.2",
"limiter": "^2.1.0",
"nodemailer": "~6.6.5",
"notp": "~2.0.3",
"password-hash": "~1.2.2",
"postcss-rtlcss": "~3.4.1",
"postcss-scss": "~4.0.1",
"prom-client": "~13.2.0",
"prometheus-api-metrics": "~3.2.0",
"qrcode": "~1.4.4",
"redbean-node": "0.1.3",
"socket.io": "~4.2.0",
"socket.io-client": "~4.2.0",
"tar": "^6.1.11",
"tcp-ping": "~0.1.1",
"thirty-two": "~1.0.2",
"timezones-list": "~3.0.1",
"v-pagination-3": "~0.1.7",
"vue": "next",
"vue-chart-3": "^0.5.8",
"vue-confirm-dialog": "^1.0.2",
"vue-contenteditable": "^3.0.4",
"vue-i18n": "^9.1.7",
"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",
"vuedraggable": "^4.1.0"
"vue-chart-3": "~0.5.11",
"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",
"vuedraggable": "~4.1.0"
},
"devDependencies": {
"@babel/eslint-parser": "^7.15.7",
"@types/bootstrap": "^5.1.6",
"@vitejs/plugin-legacy": "^1.5.3",
"@vitejs/plugin-vue": "^1.9.1",
"@vue/compiler-sfc": "^3.2.16",
"core-js": "^3.18.0",
"cross-env": "^7.0.3",
"dns2": "^2.0.1",
"eslint": "^7.32.0",
"eslint-plugin-vue": "^7.18.0",
"sass": "^1.42.1",
"stylelint": "^13.13.1",
"stylelint-config-standard": "^22.0.0",
"typescript": "^4.4.3",
"vite": "^2.5.10"
"@babel/eslint-parser": "~7.15.7",
"@babel/preset-env": "^7.15.8",
"@types/bootstrap": "~5.1.6",
"@vitejs/plugin-legacy": "~1.6.2",
"@vitejs/plugin-vue": "~1.9.4",
"@vue/compiler-sfc": "~3.2.20",
"babel-plugin-rewire": "~1.2.0",
"core-js": "~3.18.1",
"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",
"sass": "~1.42.1",
"stylelint": "~13.13.1",
"stylelint-config-standard": "~22.0.0",
"typescript": "~4.4.3",
"vite": "~2.6.13"
}
}

32
server/auth.js

@ -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");
/**
*
@ -13,7 +14,7 @@ const { debug } = require("../src/util");
exports.login = async function (username, password) {
let user = await R.findOne("user", " username = ? AND active = 1 ", [
username,
])
]);
if (user && passwordHash.verify(password, user.password)) {
// Upgrade the hash to bcrypt
@ -27,21 +28,30 @@ exports.login = async function (username, password) {
}
return null;
}
};
function myAuthorizer(username, password, callback) {
setting("disableAuth").then((result) => {
if (result) {
callback(null, true)
callback(null, true);
} else {
exports.login(username, password).then((user) => {
callback(null, user != null)
})
}
})
// Login Rate Limit
loginRateLimiter.pass(null, 0).then((pass) => {
if (pass) {
exports.login(username, password).then((user) => {
callback(null, user != null);
if (user == null) {
loginRateLimiter.removeTokens(1);
}
});
} else {
callback(null, false);
}
});
}
});
}
exports.basicAuth = basicAuth({

15
server/check-version.js

@ -1,6 +1,5 @@
const { setSetting } = require("./util-server");
const axios = require("axios");
const { isDev } = require("../src/util");
exports.version = require("../package.json").version;
exports.latestVersion = null;
@ -10,19 +9,17 @@ 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 (res.data.slow) {
exports.latestVersion = res.data.slow;
}
exports.latestVersion = res.data.version;
console.log("Latest Version: " + exports.latestVersion);
} catch (_) { }
};

22
server/client.js

@ -4,6 +4,8 @@
const { TimeLogger } = require("../src/util");
const { R } = require("redbean-node");
const { io } = require("./server");
const { setting } = require("./util-server");
const checkVersion = require("./check-version");
async function sendNotificationList(socket) {
const timeLogger = new TimeLogger();
@ -14,10 +16,10 @@ async function sendNotificationList(socket) {
]);
for (let bean of list) {
result.push(bean.export())
result.push(bean.export());
}
io.to(socket.userID).emit("notificationList", result)
io.to(socket.userID).emit("notificationList", result);
timeLogger.print("Send Notification List");
@ -39,7 +41,7 @@ async function sendHeartbeatList(socket, monitorID, toUser = false, overwrite =
LIMIT 100
`, [
monitorID,
])
]);
let result = list.reverse();
@ -69,7 +71,7 @@ async function sendImportantHeartbeatList(socket, monitorID, toUser = false, ove
LIMIT 500
`, [
monitorID,
])
]);
timeLogger.print(`[Monitor: ${monitorID}] sendImportantHeartbeatList`);
@ -81,8 +83,18 @@ async function sendImportantHeartbeatList(socket, monitorID, toUser = false, ove
}
async function sendInfo(socket) {
socket.emit("info", {
version: checkVersion.version,
latestVersion: checkVersion.latestVersion,
primaryBaseURL: await setting("primaryBaseURL")
});
}
module.exports = {
sendNotificationList,
sendImportantHeartbeatList,
sendHeartbeatList,
}
sendInfo
};

7
server/config.js

@ -0,0 +1,7 @@
const args = require("args-parser")(process.argv);
const demoMode = args["demo"] || false;
module.exports = {
args,
demoMode
};

39
server/database.js

@ -48,10 +48,14 @@ class Database {
"patch-add-retry-interval-monitor.sql": true,
"patch-incident-table.sql": true,
"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,
}
/**
* The finally version should be 10 after merged tag feature
* The final version should be 10 after merged tag feature
* @deprecated Use patchList for any new feature
*/
static latestVersion = 10;
@ -107,9 +111,11 @@ class Database {
R.freeze(true);
await R.autoloadModels("./server/model");
await R.exec("PRAGMA foreign_keys = ON");
// 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"));
@ -128,7 +134,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 {
@ -149,8 +155,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);
@ -188,7 +194,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();
@ -229,7 +235,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");
@ -284,7 +290,7 @@ class Database {
};
process.addListener("unhandledRejection", listener);
console.log("Closing DB");
console.log("Closing the database");
while (true) {
Database.noReject = true;
@ -294,7 +300,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");
@ -309,7 +315,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);
@ -332,7 +338,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";
@ -351,7 +357,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);
}
@ -370,6 +376,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;

31
server/jobs.js

@ -0,0 +1,31 @@
const path = require("path");
const Bree = require("bree");
const { SHARE_ENV } = require("worker_threads");
const jobs = [
{
name: "clear-old-data",
interval: "at 03:14",
},
];
const initBackgroundJobs = function (args) {
const bree = new Bree({
root: path.resolve("server", "jobs"),
jobs,
worker: {
env: SHARE_ENV,
workerData: args,
},
workerMessageHandler: (message) => {
console.log("[Background Job]:", message);
}
});
bree.start();
return bree;
};
module.exports = {
initBackgroundJobs
};

40
server/jobs/clear-old-data.js

@ -0,0 +1,40 @@
const { log, exit, connectDb } = require("./util-worker");
const { R } = require("redbean-node");
const { setSetting, setting } = require("../util-server");
const DEFAULT_KEEP_PERIOD = 180;
(async () => {
await connectDb();
let period = await setting("keepDataPeriodDays");
// Set Default Period
if (period == null) {
await setSetting("keepDataPeriodDays", DEFAULT_KEEP_PERIOD, "general");
period = DEFAULT_KEEP_PERIOD;
}
// Try parse setting
let parsedPeriod;
try {
parsedPeriod = parseInt(period);
} catch (_) {
log("Failed to parse setting, resetting to default..");
await setSetting("keepDataPeriodDays", DEFAULT_KEEP_PERIOD, "general");
parsedPeriod = DEFAULT_KEEP_PERIOD;
}
log(`Clearing Data older than ${parsedPeriod} days...`);
try {
await R.exec(
"DELETE FROM heartbeat WHERE time < DATETIME('now', '-' || ? || ' days') ",
[parsedPeriod]
);
} catch (e) {
log(`Failed to clear old data: ${e.message}`);
}
exit();
})();

39
server/jobs/util-worker.js

@ -0,0 +1,39 @@
const { parentPort, workerData } = require("worker_threads");
const Database = require("../database");
const path = require("path");
const log = function (any) {
if (parentPort) {
parentPort.postMessage(any);
}
};
const exit = function (error) {
if (error && error != 0) {
process.exit(error);
} else {
if (parentPort) {
parentPort.postMessage("done");
} else {
process.exit(0);
}
}
};
const connectDb = async function () {
const dbPath = path.join(
process.env.DATA_DIR || workerData["data-dir"] || "./data/"
);
Database.init({
"data-dir": dbPath,
});
await Database.connect();
};
module.exports = {
log,
exit,
connectDb,
};

324
server/model/monitor.js

@ -7,11 +7,13 @@ 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 } = 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 { demoMode } = require("../config");
const version = require("../../package.json").version;
const apicache = require("../modules/apicache");
/**
* status:
@ -53,6 +55,9 @@ class Monitor extends BeanModel {
id: this.id,
name: this.name,
url: this.url,
method: this.method,
body: this.body,
headers: this.headers,
hostname: this.hostname,
port: this.port,
maxretries: this.maxretries,
@ -69,6 +74,7 @@ class Monitor extends BeanModel {
dns_resolve_type: this.dns_resolve_type,
dns_resolve_server: this.dns_resolve_server,
dns_last_result: this.dns_last_result,
pushToken: this.pushToken,
notificationIDList,
tags: tags,
};
@ -135,11 +141,16 @@ class Monitor extends BeanModel {
// Do not do any queries/high loading things before the "bean.ping"
let startTime = dayjs().valueOf();
let res = await axios.get(this.url, {
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": "*/*",
"User-Agent": "Uptime-Kuma/" + version,
...(this.headers ? JSON.parse(this.headers) : {}),
},
httpsAgent: new https.Agent({
maxCachedSessions: 0, // Use Custom agent to disable session reuse (https://github.com/nodejs/node/issues/3940)
@ -149,15 +160,26 @@ class Monitor extends BeanModel {
validateStatus: (status) => {
return checkStatusCode(status, this.getAcceptedStatuscodes());
},
});
};
debug(`[${this.name}] Axios Request`);
let res = await axios.request(options);
bean.msg = `${res.status} - ${res.statusText}`;
bean.ping = dayjs().valueOf() - startTime;
// 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);
@ -165,7 +187,13 @@ class Monitor extends BeanModel {
}
}
debug("Cert Info Query Time: " + (dayjs().valueOf() - certInfoStartTime) + "ms");
if (process.env.TIMELOGGER === "1") {
debug("Cert Info Query Time: " + (dayjs().valueOf() - certInfoStartTime) + "ms");
}
if (process.env.UPTIME_KUMA_LOG_RESPONSE_BODY_MONITOR_ID == this.id) {
console.log(res.data);
}
if (this.type === "http") {
bean.status = UP;
@ -236,6 +264,68 @@ class Monitor extends BeanModel {
bean.msg = dnsMessage;
bean.status = UP;
} else if (this.type === "push") { // Type: Push
const time = R.isoDateTime(dayjs.utc().subtract(this.interval, "second"));
let heartbeatCount = await R.count("heartbeat", " monitor_id = ? AND time > ? ", [
this.id,
time
]);
debug("heartbeatCount" + heartbeatCount + " " + time);
if (heartbeatCount <= 0) {
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);
return;
}
} else if (this.type === "steam") {
const steamApiUrl = "https://api.steampowered.com/IGameServersService/GetServerList/v1/";
const steamAPIKey = await setting("steamAPIKey");
const filter = `addr\\${this.hostname}:${this.port}`;
if (!steamAPIKey) {
throw new Error("Steam API Key not found");
}
let res = await axios.get(steamApiUrl, {
timeout: this.interval * 1000 * 0.8,
headers: {
"Accept": "*/*",
"User-Agent": "Uptime-Kuma/" + version,
},
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());
},
params: {
filter: filter,
key: steamAPIKey,
}
});
if (res.data.response && res.data.response.servers && res.data.response.servers.length > 0) {
bean.status = UP;
bean.msg = res.data.response.servers[0].name;
try {
bean.ping = await ping(this.hostname);
} catch (_) { }
} else {
throw new Error("Server not found on Steam");
}
} else {
bean.msg = "Unknown Monitor Type";
bean.status = PENDING;
}
if (this.isUpsideDown()) {
@ -263,61 +353,31 @@ class Monitor extends BeanModel {
}
}
// * ? -> ANY STATUS = important [isFirstBeat]
// UP -> PENDING = not important
// * UP -> DOWN = important
// UP -> UP = not important
// PENDING -> PENDING = not important
// * PENDING -> DOWN = important
// PENDING -> UP = not important
// DOWN -> PENDING = this case not exists
// DOWN -> DOWN = not important
// * DOWN -> UP = important
let isImportant = isFirstBeat ||
(previousBeat.status === UP && bean.status === DOWN) ||
(previousBeat.status === DOWN && bean.status === UP) ||
(previousBeat.status === PENDING && bean.status === DOWN);
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;
// Send only if the first beat is DOWN
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 ", [
this.id,
]);
let text;
if (bean.status === UP) {
text = "✅ Up";
} else {
text = "🔴 Down";
}
debug(`[${this.name}] sendNotification`);
await Monitor.sendNotification(isFirstBeat, this, bean);
let msg = `[${this.name}] [${text}] ${bean.msg}`;
for (let notification of notificationList) {
try {
await Notification.send(JSON.parse(notification.config), msg, await this.toJSON(), bean.toJSON());
} catch (e) {
console.error("Cannot send notification to " + notification.name);
console.log(e);
}
}
}
// Clear Status Page Cache
debug(`[${this.name}] apicache clear`);
apicache.clear();
} else {
bean.important = false;
}
let beatInterval = this.interval;
if (bean.status === UP) {
console.info(`Monitor #${this.id} '${this.name}': Successful Response: ${bean.ping} ms | Interval: ${beatInterval} seconds | Type: ${this.type}`);
} else if (bean.status === PENDING) {
if (this.retryInterval !== this.interval) {
if (this.retryInterval > 0) {
beatInterval = this.retryInterval;
}
console.warn(`Monitor #${this.id} '${this.name}': Pending: ${bean.msg} | Max retries: ${this.maxretries} | Retry: ${retries} | Retry Interval: ${beatInterval} seconds | Type: ${this.type}`);
@ -325,21 +385,58 @@ 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) {
this.heartbeatInterval = setTimeout(beat, beatInterval * 1000);
if (demoMode) {
if (beatInterval < 20) {
console.log("beat interval too low, reset to 20s");
beatInterval = 20;
}
}
debug(`[${this.name}] SetTimeout for next check.`);
this.heartbeatInterval = setTimeout(safeBeat, beatInterval * 1000);
} else {
console.log(`[${this.name}] isStop = true, no next check.`);
}
};
beat();
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(() => {
safeBeat();
}, this.interval * 1000);
} else {
safeBeat();
}
}
stop() {
@ -370,10 +467,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);
@ -500,6 +623,113 @@ class Monitor extends BeanModel {
const uptime = await this.calcUptime(duration, monitorID);
io.to(userID).emit("uptime", monitorID, duration, uptime);
}
static isImportantBeat(isFirstBeat, previousBeatStatus, currentBeatStatus) {
// * ? -> ANY STATUS = important [isFirstBeat]
// UP -> PENDING = not important
// * UP -> DOWN = important
// UP -> UP = not important
// PENDING -> PENDING = not important
// * PENDING -> DOWN = important
// PENDING -> UP = not important
// DOWN -> PENDING = this case not exists
// DOWN -> DOWN = not important
// * DOWN -> UP = important
let isImportant = isFirstBeat ||
(previousBeatStatus === UP && currentBeatStatus === DOWN) ||
(previousBeatStatus === DOWN && currentBeatStatus === UP) ||
(previousBeatStatus === PENDING && currentBeatStatus === DOWN);
return isImportant;
}
static async sendNotification(isFirstBeat, monitor, bean) {
if (!isFirstBeat || bean.status === DOWN) {
const notificationList = await Monitor.getNotificationList(monitor);
let text;
if (bean.status === UP) {
text = "✅ Up";
} else {
text = "🔴 Down";
}
let msg = `[${monitor.name}] [${text}] ${bean.msg}`;
for (let notification of notificationList) {
try {
await Notification.send(JSON.parse(notification.config), msg, await monitor.toJSON(), bean.toJSON());
} catch (e) {
console.error("Cannot send notification to " + notification.name);
console.log(e);
}
}
}
}
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");
}
}
}
module.exports = Monitor;

108
server/notification-providers/aliyun-sms.js

@ -0,0 +1,108 @@
const NotificationProvider = require("./notification-provider");
const { DOWN, UP } = require("../../src/util");
const { default: axios } = require("axios");
const Crypto = require("crypto");
const qs = require("qs");
class AliyunSMS extends NotificationProvider {
name = "AliyunSMS";
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
let okMsg = "Sent Successfully.";
try {
if (heartbeatJSON != null) {
let msgBody = JSON.stringify({
name: monitorJSON["name"],
time: heartbeatJSON["time"],
status: this.statusToString(heartbeatJSON["status"]),
msg: heartbeatJSON["msg"],
});
if (this.sendSms(notification, msgBody)) {
return okMsg;
}
} else {
let msgBody = JSON.stringify({
name: "",
time: "",
status: "",
msg: msg,
});
if (this.sendSms(notification, msgBody)) {
return okMsg;
}
}
} catch (error) {
this.throwGeneralAxiosError(error);
}
}
async sendSms(notification, msgbody) {
let params = {
PhoneNumbers: notification.phonenumber,
TemplateCode: notification.templateCode,
SignName: notification.signName,
TemplateParam: msgbody,
AccessKeyId: notification.accessKeyId,
Format: "JSON",
SignatureMethod: "HMAC-SHA1",
SignatureVersion: "1.0",
SignatureNonce: Math.random().toString(),
Timestamp: new Date().toISOString(),
Action: "SendSms",
Version: "2017-05-25",
};
params.Signature = this.sign(params, notification.secretAccessKey);
let config = {
method: "POST",
url: "http://dysmsapi.aliyuncs.com/",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
data: qs.stringify(params),
};
let result = await axios(config);
if (result.data.Message == "OK") {
return true;
}
return false;
}
/** Aliyun request sign */
sign(param, AccessKeySecret) {
let param2 = {};
let data = [];
let oa = Object.keys(param).sort();
for (let i = 0; i < oa.length; i++) {
let key = oa[i];
param2[key] = param[key];
}
for (let key in param2) {
data.push(`${encodeURIComponent(key)}=${encodeURIComponent(param2[key])}`);
}
let StringToSign = `POST&${encodeURIComponent("/")}&${encodeURIComponent(data.join("&"))}`;
return Crypto
.createHmac("sha1", `${AccessKeySecret}&`)
.update(Buffer.from(StringToSign))
.digest("base64");
}
statusToString(status) {
switch (status) {
case DOWN:
return "DOWN";
case UP:
return "UP";
default:
return status;
}
}
}
module.exports = AliyunSMS;

89
server/notification-providers/bark.js

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

42
server/notification-providers/clicksendsms.js

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

79
server/notification-providers/dingding.js

@ -0,0 +1,79 @@
const NotificationProvider = require("./notification-provider");
const { DOWN, UP } = require("../../src/util");
const { default: axios } = require("axios");
const Crypto = require("crypto");
class DingDing extends NotificationProvider {
name = "DingDing";
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
let okMsg = "Sent Successfully.";
try {
if (heartbeatJSON != null) {
let params = {
msgtype: "markdown",
markdown: {
title: monitorJSON["name"],
text: `## [${this.statusToString(heartbeatJSON["status"])}] \n > ${heartbeatJSON["msg"]} \n > Time(UTC):${heartbeatJSON["time"]}`,
}
};
if (this.sendToDingDing(notification, params)) {
return okMsg;
}
} else {
let params = {
msgtype: "text",
text: {
content: msg
}
};
if (this.sendToDingDing(notification, params)) {
return okMsg;
}
}
} catch (error) {
this.throwGeneralAxiosError(error);
}
}
async sendToDingDing(notification, params) {
let timestamp = Date.now();
let config = {
method: "POST",
headers: {
"Content-Type": "application/json",
},
url: `${notification.webHookUrl}&timestamp=${timestamp}&sign=${encodeURIComponent(this.sign(timestamp, notification.secretKey))}`,
data: JSON.stringify(params),
};
let result = await axios(config);
if (result.data.errmsg == "ok") {
return true;
}
return false;
}
/** DingDing sign */
sign(timestamp, secretKey) {
return Crypto
.createHmac("sha256", Buffer.from(secretKey, "utf8"))
.update(Buffer.from(`${timestamp}\n${secretKey}`, "utf8"))
.digest("base64");
}
statusToString(status) {
switch (status) {
case DOWN:
return "DOWN";
case UP:
return "UP";
default:
return status;
}
}
}
module.exports = DingDing;

2
server/notification-providers/discord.js

@ -7,7 +7,7 @@ class Discord extends NotificationProvider {
name = "discord";
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
let okMsg = "Sent Successfully. ";
let okMsg = "Sent Successfully.";
try {
const discordDisplayName = notification.discordUsername || "Uptime Kuma";

83
server/notification-providers/feishu.js

@ -0,0 +1,83 @@
const NotificationProvider = require("./notification-provider");
const axios = require("axios");
const { DOWN, UP } = require("../../src/util");
class Feishu extends NotificationProvider {
name = "Feishu";
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
let okMsg = "Sent Successfully.";
let feishuWebHookUrl = notification.feishuWebHookUrl;
try {
if (heartbeatJSON == null) {
let testdata = {
msg_type: "text",
content: {
text: msg,
},
};
await axios.post(feishuWebHookUrl, testdata);
return okMsg;
}
if (heartbeatJSON["status"] == DOWN) {
let downdata = {
msg_type: "post",
content: {
post: {
zh_cn: {
title: "UptimeKuma Alert: [Down] " + monitorJSON["name"],
content: [
[
{
tag: "text",
text:
"[Down] " +
heartbeatJSON["msg"] +
"\nTime (UTC): " +
heartbeatJSON["time"],
},
],
],
},
},
},
};
await axios.post(feishuWebHookUrl, downdata);
return okMsg;
}
if (heartbeatJSON["status"] == UP) {
let updata = {
msg_type: "post",
content: {
post: {
zh_cn: {
title: "UptimeKuma Alert: [Up] " + monitorJSON["name"],
content: [
[
{
tag: "text",
text:
"[Up] " +
heartbeatJSON["msg"] +
"\nTime (UTC): " +
heartbeatJSON["time"],
},
],
],
},
},
},
};
await axios.post(feishuWebHookUrl, updata);
return okMsg;
}
} catch (error) {
this.throwGeneralAxiosError(error);
}
}
}
module.exports = Feishu;

2
server/notification-providers/gotify.js

@ -6,7 +6,7 @@ class Gotify extends NotificationProvider {
name = "gotify";
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
let okMsg = "Sent Successfully. ";
let okMsg = "Sent Successfully.";
try {
if (notification.gotifyserverurl && notification.gotifyserverurl.endsWith("/")) {
notification.gotifyserverurl = notification.gotifyserverurl.slice(0, -1);

2
server/notification-providers/line.js

@ -7,7 +7,7 @@ class Line extends NotificationProvider {
name = "line";
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
let okMsg = "Sent Successfully. ";
let okMsg = "Sent Successfully.";
try {
let lineAPIUrl = "https://api.line.me/v2/bot/message/push";
let config = {

2
server/notification-providers/lunasea.js

@ -7,7 +7,7 @@ class LunaSea extends NotificationProvider {
name = "lunasea";
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
let okMsg = "Sent Successfully. ";
let okMsg = "Sent Successfully.";
let lunaseadevice = "https://notify.lunasea.app/v1/custom/device/" + notification.lunaseaDevice
try {

45
server/notification-providers/matrix.js

@ -0,0 +1,45 @@
const NotificationProvider = require("./notification-provider");
const axios = require("axios");
const Crypto = require("crypto");
const { debug } = require("../../src/util");
class Matrix extends NotificationProvider {
name = "matrix";
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
let okMsg = "Sent Successfully.";
const size = 20;
const randomString = encodeURIComponent(
Crypto
.randomBytes(size)
.toString("base64")
.slice(0, size)
);
debug("Random String: " + randomString);
const roomId = encodeURIComponent(notification.internalRoomId);
debug("Matrix Room ID: " + roomId);
try {
let config = {
headers: {
"Authorization": `Bearer ${notification.accessToken}`,
}
};
let data = {
"msgtype": "m.text",
"body": msg
};
await axios.put(`${notification.homeserverUrl}/_matrix/client/r0/rooms/${roomId}/send/m.room.message/${randomString}`, data, config);
return okMsg;
} catch (error) {
this.throwGeneralAxiosError(error);
}
}
}
module.exports = Matrix;

2
server/notification-providers/mattermost.js

@ -7,7 +7,7 @@ class Mattermost extends NotificationProvider {
name = "mattermost";
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
let okMsg = "Sent Successfully. ";
let okMsg = "Sent Successfully.";
try {
const mattermostUserName = notification.mattermostusername || "Uptime Kuma";
// If heartbeatJSON is null, assume we're testing.

64
server/notification-providers/octopush.js

@ -6,30 +6,54 @@ class Octopush extends NotificationProvider {
name = "octopush";
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
let okMsg = "Sent Successfully. ";
let okMsg = "Sent Successfully.";
try {
let config = {
headers: {
"api-key": notification.octopushAPIKey,
"api-login": notification.octopushLogin,
"cache-control": "no-cache"
}
};
let data = {
"recipients": [
{
"phone_number": notification.octopushPhoneNumber
// Default - V2
if (notification.octopushVersion == 2 || !notification.octopushVersion) {
let config = {
headers: {
"api-key": notification.octopushAPIKey,
"api-login": notification.octopushLogin,
"cache-control": "no-cache"
}
],
//octopush not supporting non ascii char
"text": msg.replace(/[^\x00-\x7F]/g, ""),
"type": notification.octopushSMSType,
"purpose": "alert",
"sender": notification.octopushSenderName
};
};
let data = {
"recipients": [
{
"phone_number": notification.octopushPhoneNumber
}
],
//octopush not supporting non ascii char
"text": msg.replace(/[^\x00-\x7F]/g, ""),
"type": notification.octopushSMSType,
"purpose": "alert",
"sender": notification.octopushSenderName
};
await axios.post("https://api.octopush.com/v1/public/sms-campaign/send", data, config)
} else if (notification.octopushVersion == 1) {
let data = {
"user_login": notification.octopushDMLogin,
"api_key": notification.octopushDMAPIKey,
"sms_recipients": notification.octopushDMPhoneNumber,
"sms_sender": notification.octopushDMSenderName,
"sms_type": (notification.octopushDMSMSType == "sms_premium") ? "FR" : "XXX",
"transactional": "1",
//octopush not supporting non ascii char
"sms_text": msg.replace(/[^\x00-\x7F]/g, ""),
};
let config = {
headers: {
"cache-control": "no-cache"
},
params: data
};
await axios.post("https://www.octopush-dm.com/api/sms/json", {}, config)
} else {
throw new Error("Unknown Octopush version!");
}
await axios.post("https://api.octopush.com/v1/public/sms-campaign/send", data, config)
return okMsg;
} catch (error) {
this.throwGeneralAxiosError(error);

41
server/notification-providers/promosms.js

@ -0,0 +1,41 @@
const NotificationProvider = require("./notification-provider");
const axios = require("axios");
class PromoSMS extends NotificationProvider {
name = "promosms";
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
let okMsg = "Sent Successfully.";
try {
let config = {
headers: {
"Content-Type": "application/json",
"Authorization": "Basic " + Buffer.from(notification.promosmsLogin + ":" + notification.promosmsPassword).toString('base64'),
"Accept": "text/json",
}
};
let data = {
"recipients": [ notification.promosmsPhoneNumber ],
//Lets remove non ascii char
"text": msg.replace(/[^\x00-\x7F]/g, ""),
"type": Number(notification.promosmsSMSType),
"sender": notification.promosmsSenderName
};
let resp = await axios.post("https://promosms.com/api/rest/v3_2/sms", data, config);
if (resp.data.response.status !== 0) {
let error = "Something gone wrong. Api returned " + resp.data.response.status + ".";
this.throwGeneralAxiosError(error);
}
return okMsg;
} catch (error) {
this.throwGeneralAxiosError(error);
}
}
}
module.exports = PromoSMS;

2
server/notification-providers/pushbullet.js

@ -8,7 +8,7 @@ class Pushbullet extends NotificationProvider {
name = "pushbullet";
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
let okMsg = "Sent Successfully. ";
let okMsg = "Sent Successfully.";
try {
let pushbulletUrl = "https://api.pushbullet.com/v2/pushes";

2
server/notification-providers/pushover.js

@ -6,7 +6,7 @@ class Pushover extends NotificationProvider {
name = "pushover";
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
let okMsg = "Sent Successfully. ";
let okMsg = "Sent Successfully.";
let pushoverlink = "https://api.pushover.net/1/messages.json"
try {

2
server/notification-providers/pushy.js

@ -6,7 +6,7 @@ class Pushy extends NotificationProvider {
name = "pushy";
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
let okMsg = "Sent Successfully. ";
let okMsg = "Sent Successfully.";
try {
await axios.post(`https://api.pushy.me/push?api_key=${notification.pushyAPIKey}`, {

36
server/notification-providers/rocket-chat.js

@ -1,25 +1,29 @@
const NotificationProvider = require("./notification-provider");
const axios = require("axios");
const Slack = require("./slack");
const { setting } = require("../util-server");
const { getMonitorRelativeURL, UP, DOWN } = require("../../src/util");
class RocketChat extends NotificationProvider {
name = "rocket.chat";
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
let okMsg = "Sent Successfully. ";
let okMsg = "Sent Successfully.";
try {
if (heartbeatJSON == null) {
let data = {
"text": "Uptime Kuma Rocket.chat testing successful.",
"text": msg,
"channel": notification.rocketchannel,
"username": notification.rocketusername,
"icon_emoji": notification.rocketiconemo,
}
await axios.post(notification.rocketwebhookURL, data)
};
await axios.post(notification.rocketwebhookURL, data);
return okMsg;
}
const time = heartbeatJSON["time"];
let data = {
"text": "Uptime Kuma Alert",
"channel": notification.rocketchannel,
@ -28,16 +32,32 @@ class RocketChat extends NotificationProvider {
"attachments": [
{
"title": "Uptime Kuma Alert *Time (UTC)*\n" + time,
"title_link": notification.rocketbutton,
"text": "*Message*\n" + msg,
"color": "#32cd32"
}
]
};
// Color
if (heartbeatJSON.status === DOWN) {
data.attachments[0].color = "#ff0000";
} else {
data.attachments[0].color = "#32cd32";
}
if (notification.rocketbutton) {
await Slack.deprecateURL(notification.rocketbutton);
}
await axios.post(notification.rocketwebhookURL, data)
const baseURL = await setting("primaryBaseURL");
if (baseURL) {
data.attachments[0].title_link = baseURL + getMonitorRelativeURL(monitorJSON.id);
}
await axios.post(notification.rocketwebhookURL, data);
return okMsg;
} catch (error) {
this.throwGeneralAxiosError(error)
this.throwGeneralAxiosError(error);
}
}

2
server/notification-providers/signal.js

@ -6,7 +6,7 @@ class Signal extends NotificationProvider {
name = "signal";
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
let okMsg = "Sent Successfully. ";
let okMsg = "Sent Successfully.";
try {
let data = {

69
server/notification-providers/slack.js

@ -1,27 +1,47 @@
const NotificationProvider = require("./notification-provider");
const axios = require("axios");
const { setSettings, setting } = require("../util-server");
const { getMonitorRelativeURL } = require("../../src/util");
class Slack extends NotificationProvider {
name = "slack";
/**
* Deprecated property notification.slackbutton
* Set it as primary base url if this is not yet set.
*/
static async deprecateURL(url) {
let currentPrimaryBaseURL = await setting("primaryBaseURL");
if (!currentPrimaryBaseURL) {
console.log("Move the url to be the primary base URL");
await setSettings("general", {
primaryBaseURL: url,
});
} else {
console.log("Already there, no need to move the primary base URL");
}
}
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
let okMsg = "Sent Successfully. ";
let okMsg = "Sent Successfully.";
try {
if (heartbeatJSON == null) {
let data = {
"text": "Uptime Kuma Slack testing successful.",
"text": msg,
"channel": notification.slackchannel,
"username": notification.slackusername,
"icon_emoji": notification.slackiconemo,
}
await axios.post(notification.slackwebhookURL, data)
};
await axios.post(notification.slackwebhookURL, data);
return okMsg;
}
const time = heartbeatJSON["time"];
const textMsg = "Uptime Kuma Alert";
let data = {
"text": "Uptime Kuma Alert",
"text": monitorJSON ? textMsg + `: ${monitorJSON.name}` : textMsg,
"channel": notification.slackchannel,
"username": notification.slackusername,
"icon_emoji": notification.slackiconemo,
@ -42,26 +62,35 @@ class Slack extends NotificationProvider {
"type": "mrkdwn",
"text": "*Time (UTC)*\n" + time,
}],
},
{
}],
};
if (notification.slackbutton) {
await Slack.deprecateURL(notification.slackbutton);
}
const baseURL = await setting("primaryBaseURL");
// Button
if (baseURL) {
data.blocks.push({
"type": "actions",
"elements": [
{
"type": "button",
"text": {
"type": "plain_text",
"text": "Visit Uptime Kuma",
},
"value": "Uptime-Kuma",
"url": notification.slackbutton || "https://github.com/louislam/uptime-kuma",
"elements": [{
"type": "button",
"text": {
"type": "plain_text",
"text": "Visit Uptime Kuma",
},
],
}],
"value": "Uptime-Kuma",
"url": baseURL + getMonitorRelativeURL(monitorJSON.id),
}],
});
}
await axios.post(notification.slackwebhookURL, data)
await axios.post(notification.slackwebhookURL, data);
return okMsg;
} catch (error) {
this.throwGeneralAxiosError(error)
this.throwGeneralAxiosError(error);
}
}

59
server/notification-providers/smtp.js

@ -1,5 +1,6 @@
const nodemailer = require("nodemailer");
const NotificationProvider = require("./notification-provider");
const { DOWN, UP } = require("../../src/util");
class SMTP extends NotificationProvider {
@ -11,6 +12,9 @@ class SMTP extends NotificationProvider {
host: notification.smtpHost,
port: notification.smtpPort,
secure: notification.smtpSecure,
tls: {
rejectUnauthorized: notification.smtpIgnoreTLSError || false,
},
};
// Should fix the issue in https://github.com/louislam/uptime-kuma/issues/26#issuecomment-896373904
@ -20,6 +24,56 @@ class SMTP extends NotificationProvider {
pass: notification.smtpPassword,
};
}
// Lets start with default subject and empty string for custom one
let subject = msg;
// Change the subject if:
// - The msg ends with "Testing" or
// - Actual Up/Down Notification
if ((monitorJSON && heartbeatJSON) || msg.endsWith("Testing")) {
let customSubject = "";
// Our subject cannot end with whitespace it's often raise spam score
// Once I got "Cannot read property 'trim' of undefined", better be safe than sorry
if (notification.customSubject) {
customSubject = notification.customSubject.trim();
}
// If custom subject is not empty, change subject for notification
if (customSubject !== "") {
// Replace "MACROS" with corresponding variable
let replaceName = new RegExp("{{NAME}}", "g");
let replaceHostnameOrURL = new RegExp("{{HOSTNAME_OR_URL}}", "g");
let replaceStatus = new RegExp("{{STATUS}}", "g");
// Lets start with dummy values to simplify code
let monitorName = "Test";
let monitorHostnameOrURL = "testing.hostname";
let serviceStatus = "⚠️ Test";
if (monitorJSON !== null) {
monitorName = monitorJSON["name"];
if (monitorJSON["type"] === "http" || monitorJSON["type"] === "keyword") {
monitorHostnameOrURL = monitorJSON["url"];
} else {
monitorHostnameOrURL = monitorJSON["hostname"];
}
}
if (heartbeatJSON !== null) {
serviceStatus = (heartbeatJSON["status"] === DOWN) ? "🔴 Down" : "✅ Up";
}
// Break replace to one by line for better readability
customSubject = customSubject.replace(replaceStatus, serviceStatus);
customSubject = customSubject.replace(replaceName, monitorName);
customSubject = customSubject.replace(replaceHostnameOrURL, monitorHostnameOrURL);
subject = customSubject;
}
}
let transporter = nodemailer.createTransport(config);
@ -34,11 +88,8 @@ class SMTP extends NotificationProvider {
cc: notification.smtpCC,
bcc: notification.smtpBCC,
to: notification.smtpTo,
subject: msg,
subject: subject,
text: bodyTextContent,
tls: {
rejectUnauthorized: notification.smtpIgnoreTLSError || false,
},
});
return "Sent Successfully.";

2
server/notification-providers/teams.js

@ -87,7 +87,7 @@ class Teams extends NotificationProvider {
};
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
let okMsg = "Sent Successfully. ";
let okMsg = "Sent Successfully.";
try {
if (heartbeatJSON == null) {

2
server/notification-providers/telegram.js

@ -6,7 +6,7 @@ class Telegram extends NotificationProvider {
name = "telegram";
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
let okMsg = "Sent Successfully. ";
let okMsg = "Sent Successfully.";
try {
await axios.get(`https://api.telegram.org/bot${notification.telegramBotToken}/sendMessage`, {

2
server/notification-providers/webhook.js

@ -7,7 +7,7 @@ class Webhook extends NotificationProvider {
name = "webhook";
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
let okMsg = "Sent Successfully. ";
let okMsg = "Sent Successfully.";
try {
let data = {

14
server/notification.js

@ -5,7 +5,10 @@ const Gotify = require("./notification-providers/gotify");
const Line = require("./notification-providers/line");
const LunaSea = require("./notification-providers/lunasea");
const Mattermost = require("./notification-providers/mattermost");
const 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");
@ -16,6 +19,10 @@ const SMTP = require("./notification-providers/smtp");
const Teams = require("./notification-providers/teams");
const Telegram = require("./notification-providers/telegram");
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");
class Notification {
@ -28,13 +35,19 @@ class Notification {
const list = [
new Apprise(),
new AliyunSms(),
new DingDing(),
new Discord(),
new Teams(),
new Gotify(),
new Line(),
new LunaSea(),
new Feishu(),
new Mattermost(),
new Matrix(),
new Octopush(),
new PromoSMS(),
new ClickSendSMS(),
new Pushbullet(),
new Pushover(),
new Pushy(),
@ -44,6 +57,7 @@ class Notification {
new SMTP(),
new Telegram(),
new Webhook(),
new Bark(),
];
for (let item of list) {

35
server/ping-lite.js

@ -4,10 +4,7 @@ const net = require("net");
const spawn = require("child_process").spawn;
const events = require("events");
const fs = require("fs");
const WIN = /^win/.test(process.platform);
const LIN = /^linux/.test(process.platform);
const MAC = /^darwin/.test(process.platform);
const FBSD = /^freebsd/.test(process.platform);
const util = require("./util-server");
module.exports = Ping;
@ -23,12 +20,12 @@ function Ping(host, options) {
const timeout = 10;
if (WIN) {
if (util.WIN) {
this._bin = "c:/windows/system32/ping.exe";
this._args = (options.args) ? options.args : [ "-n", "1", "-w", timeout * 1000, host ];
this._regmatch = /[><=]([0-9.]+?)ms/;
} else if (LIN) {
} else if (util.LIN) {
this._bin = "/bin/ping";
const defaultArgs = [ "-n", "-w", timeout, "-c", "1", host ];
@ -40,7 +37,7 @@ function Ping(host, options) {
this._args = (options.args) ? options.args : defaultArgs;
this._regmatch = /=([0-9.]+?) ms/;
} else if (MAC) {
} else if (util.MAC) {
if (net.isIPv6(host) || options.ipv6) {
this._bin = "/sbin/ping6";
@ -51,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 (FBSD) {
} else if (util.FBSD) {
this._bin = "/sbin/ping";
const defaultArgs = [ "-n", "-t", timeout, "-c", "1", host ];
@ -101,6 +98,9 @@ Ping.prototype.send = function (callback) {
});
this._ping.stdout.on("data", function (data) { // log stdout
if (util.WIN) {
data = convertOutput(data);
}
this._stdout = (this._stdout || "") + data;
});
@ -112,6 +112,9 @@ Ping.prototype.send = function (callback) {
});
this._ping.stderr.on("data", function (data) { // log stderr
if (util.WIN) {
data = convertOutput(data);
}
this._stderr = (this._stderr || "") + data;
});
@ -157,3 +160,19 @@ Ping.prototype.start = function (callback) {
Ping.prototype.stop = function () {
clearInterval(this._i);
};
/**
* Try to convert to UTF-8 for Windows, as the ping's output on Windows is not UTF-8 and could be in other languages
* Thank @pemassi
* https://github.com/louislam/uptime-kuma/issues/570#issuecomment-941984094
* @param data
* @returns {string}
*/
function convertOutput(data) {
if (util.WIN) {
if (data) {
return util.convertToUTF8(data);
}
}
return data;
}

31
server/prometheus.js

@ -6,7 +6,7 @@ const commonLabels = [
"monitor_url",
"monitor_hostname",
"monitor_port",
]
];
const monitor_cert_days_remaining = new PrometheusClient.Gauge({
name: "monitor_cert_days_remaining",
@ -41,45 +41,46 @@ class Prometheus {
monitor_url: monitor.url,
monitor_hostname: monitor.hostname,
monitor_port: monitor.port
}
};
}
update(heartbeat, tlsInfo) {
if (typeof tlsInfo !== "undefined") {
try {
let is_valid = 0
let is_valid = 0;
if (tlsInfo.valid == true) {
is_valid = 1
is_valid = 1;
} else {
is_valid = 0
is_valid = 0;
}
monitor_cert_is_valid.set(this.monitorLabelValues, is_valid)
monitor_cert_is_valid.set(this.monitorLabelValues, is_valid);
} catch (e) {
console.error(e)
console.error(e);
}
try {
monitor_cert_days_remaining.set(this.monitorLabelValues, tlsInfo.daysRemaining)
monitor_cert_days_remaining.set(this.monitorLabelValues, tlsInfo.certInfo.daysRemaining);
} catch (e) {
console.error(e)
console.error(e);
}
}
try {
monitor_status.set(this.monitorLabelValues, heartbeat.status)
monitor_status.set(this.monitorLabelValues, heartbeat.status);
} catch (e) {
console.error(e)
console.error(e);
}
try {
if (typeof heartbeat.ping === "number") {
monitor_response_time.set(this.monitorLabelValues, heartbeat.ping)
monitor_response_time.set(this.monitorLabelValues, heartbeat.ping);
} else {
// Is it good?
monitor_response_time.set(this.monitorLabelValues, -1)
monitor_response_time.set(this.monitorLabelValues, -1);
}
} catch (e) {
console.error(e)
console.error(e);
}
}
@ -87,4 +88,4 @@ class Prometheus {
module.exports = {
Prometheus
}
};

39
server/rate-limiter.js

@ -0,0 +1,39 @@
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."
});
module.exports = {
loginRateLimiter
};

99
server/routers/api-router.js

@ -4,15 +4,89 @@ const { R } = require("redbean-node");
const server = require("../server");
const apicache = require("../modules/apicache");
const Monitor = require("../model/monitor");
const dayjs = require("dayjs");
const { UP, flipStatus, debug } = require("../../src/util");
let router = express.Router();
let cache = apicache.middleware;
let io = server.io;
router.get("/api/entry-page", async (_, response) => {
allowDevAllOrigin(response);
response.json(server.entryPage);
});
router.get("/api/push/:pushToken", async (request, response) => {
try {
let pushToken = request.params.pushToken;
let msg = request.query.msg || "OK";
let ping = request.query.ping || null;
let monitor = await R.findOne("monitor", " push_token = ? AND active = 1 ", [
pushToken
]);
if (! monitor) {
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
]);
let status = UP;
if (monitor.isUpsideDown()) {
status = flipStatus(status);
}
let isFirstBeat = true;
let previousStatus = status;
let duration = 0;
let bean = R.dispense("heartbeat");
bean.time = R.isoDateTime(dayjs.utc());
if (previousHeartbeat) {
isFirstBeat = false;
previousStatus = previousHeartbeat.status;
duration = dayjs(bean.time).diff(dayjs(previousHeartbeat.time), "second");
}
debug("PreviousStatus: " + previousStatus);
debug("Current Status: " + status);
bean.important = Monitor.isImportantBeat(isFirstBeat, previousStatus, status);
bean.monitor_id = monitor.id;
bean.status = status;
bean.msg = msg;
bean.ping = ping;
bean.duration = duration;
await R.store(bean);
io.to(monitor.user_id).emit("heartbeat", bean.toJSON());
Monitor.sendStats(io, monitor.id, monitor.user_id);
response.json({
ok: true,
});
if (bean.important) {
await Monitor.sendNotification(isFirstBeat, monitor, bean);
}
} catch (e) {
response.json({
ok: false,
msg: e.message
});
}
});
// Status Page Config
router.get("/api/status-page/config", async (_request, response) => {
allowDevAllOrigin(response);
@ -27,6 +101,10 @@ router.get("/api/status-page/config", async (_request, response) => {
config.statusPagePublished = true;
}
if (! config.statusPageTags) {
config.statusPageTags = false;
}
if (! config.title) {
config.title = "Uptime Kuma";
}
@ -66,10 +144,25 @@ router.get("/api/status-page/monitor-list", cache("5 minutes"), async (_request,
try {
await checkPublished();
const publicGroupList = [];
let list = await R.find("group", " public = 1 ORDER BY weight ");
const tagsVisible = (await getSettings("statusPage")).statusPageTags;
const list = await R.find("group", " public = 1 ORDER BY weight ");
for (let groupBean of list) {
publicGroupList.push(await groupBean.toPublicJSON());
let monitorGroup = await groupBean.toPublicJSON();
if (tagsVisible) {
monitorGroup.monitorList = await Promise.all(monitorGroup.monitorList.map(async (monitor) => {
// Includes tags as an array in response, allows for tags to be displayed on public status page
const tags = await R.getAll(
`SELECT monitor_tag.monitor_id, monitor_tag.value, tag.name, tag.color
FROM monitor_tag
JOIN tag
ON monitor_tag.tag_id = tag.id
WHERE monitor_tag.monitor_id = ?`, [monitor.id]
);
return {...monitor, tags: tags}
}));
}
publicGroupList.push(monitorGroup);
}
response.json(publicGroupList);

198
server/server.js

@ -1,4 +1,9 @@
console.log("Welcome to Uptime Kuma");
const args = require("args-parser")(process.argv);
const { sleep, debug, getRandomInt, genSecret } = require("../src/util");
const config = require("./config");
debug(args);
if (! process.env.NODE_ENV) {
process.env.NODE_ENV = "production";
@ -6,8 +11,6 @@ if (! process.env.NODE_ENV) {
console.log("Node Env: " + process.env.NODE_ENV);
const { sleep, debug, TimeLogger, getRandomInt } = require("../src/util");
console.log("Importing Node libraries");
const fs = require("fs");
const http = require("http");
@ -28,6 +31,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");
@ -37,7 +41,7 @@ console.log("Importing this project modules");
debug("Importing Monitor");
const Monitor = require("./model/monitor");
debug("Importing Settings");
const { getSettings, setSettings, setting, initJWTSecret, genSecret, allowDevAllOrigin, checkLogin } = require("./util-server");
const { getSettings, setSettings, setting, initJWTSecret, checkLogin, startUnitTest, FBSD, errorLog } = require("./util-server");
debug("Importing Notification");
const { Notification } = require("./notification");
@ -46,32 +50,54 @@ Notification.init();
debug("Importing Database");
const Database = require("./database");
debug("Importing Background Jobs");
const { initBackgroundJobs } = require("./jobs");
const { loginRateLimiter } = require("./rate-limiter");
const { basicAuth } = require("./auth");
const { login } = require("./auth");
const passwordHash = require("./password-hash");
const args = require("args-parser")(process.argv);
const checkVersion = require("./check-version");
console.info("Version: " + checkVersion.version);
// 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 (::)
const hostname = process.env.HOST || args.host;
const port = parseInt(process.env.PORT || args.port || 3001);
let hostname = process.env.UPTIME_KUMA_HOST || args.host;
// Also read HOST if not FreeBSD, as HOST is a system environment variable in FreeBSD
if (!hostname && !FBSD) {
hostname = process.env.HOST;
}
if (hostname) {
console.log("Custom hostname: " + hostname);
}
const port = parseInt(process.env.UPTIME_KUMA_PORT || process.env.PORT || args.port || 3001);
// SSL
const sslKey = process.env.SSL_KEY || args["ssl-key"] || undefined;
const sslCert = process.env.SSL_CERT || args["ssl-cert"] || undefined;
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;
// Demo Mode?
const demoMode = args["demo"] || false;
// 2FA / notp verification defaults
const twofa_verification_opts = {
"window": 1,
"time": 30
};
/**
* Run unit test after the server is ready
* @type {boolean}
*/
const testMode = !!args["test"] || false;
if (demoMode) {
if (config.demoMode) {
console.log("==== Demo Mode ====");
}
console.log("Creating express and socket.io instance")
console.log("Creating express and socket.io instance");
const app = express();
let server;
@ -91,11 +117,21 @@ const io = new Server(server);
module.exports.io = io;
// Must be after io instantiation
const { sendNotificationList, sendHeartbeatList, sendImportantHeartbeatList } = require("./client");
const { sendNotificationList, sendHeartbeatList, sendImportantHeartbeatList, sendInfo } = require("./client");
const { statusPageSocketHandler } = require("./socket-handlers/status-page-socket-handler");
const databaseSocketHandler = require("./socket-handlers/database-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}
@ -124,7 +160,17 @@ 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";
@ -140,6 +186,15 @@ exports.entryPage = "dashboard";
// Normal Router here
// ***************************
// Entry Page
app.get("/", async (_request, response) => {
if (exports.entryPage === "statusPage") {
response.redirect("/status");
} else {
response.redirect("/dashboard");
}
});
// Robots.txt
app.get("/robots.txt", async (_request, response) => {
let txt = "User-agent: *\nDisallow:";
@ -169,7 +224,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.");
@ -181,10 +236,7 @@ exports.entryPage = "dashboard";
console.log("Adding socket handler");
io.on("connection", async (socket) => {
socket.emit("info", {
version: checkVersion.version,
latestVersion: checkVersion.latestVersion,
});
sendInfo(socket);
totalClient++;
@ -240,12 +292,16 @@ exports.entryPage = "dashboard";
socket.on("login", async (data, callback) => {
console.log("Login");
// Login Rate Limit
if (! await loginRateLimiter.pass(callback)) {
return;
}
let user = await login(data.username, data.password);
if (user) {
afterLogin(socket, user);
if (user.twofaStatus == 0) {
if (user.twofa_status == 0) {
afterLogin(socket, user);
callback({
ok: true,
token: jwt.sign({
@ -254,16 +310,23 @@ exports.entryPage = "dashboard";
});
}
if (user.twofaStatus == 1 && !data.token) {
if (user.twofa_status == 1 && !data.token) {
callback({
tokenRequired: true,
});
}
if (data.token) {
let verify = notp.totp.verify(data.token, user.twofa_secret);
let verify = notp.totp.verify(data.token, user.twofa_secret, twofa_verification_opts);
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,
]);
if (verify && verify.delta == 0) {
callback({
ok: true,
token: jwt.sign({
@ -301,8 +364,14 @@ exports.entryPage = "dashboard";
]);
if (user.twofa_status == 0) {
let newSecret = await genSecret();
let newSecret = genSecret();
let encodedSecret = base32.encode(newSecret);
// Google authenticator doesn't like equal signs
// The fix is found at https://github.com/guyht/notp
// Related issue: https://github.com/louislam/uptime-kuma/issues/486
encodedSecret = encodedSecret.toString().replace(/=/g, "");
let uri = `otpauth://totp/Uptime%20Kuma:${user.username}?secret=${encodedSecret}`;
await R.exec("UPDATE `user` SET twofa_secret = ? WHERE id = ? ", [
@ -373,9 +442,9 @@ exports.entryPage = "dashboard";
socket.userID,
]);
let verify = notp.totp.verify(token, user.twofa_secret);
let verify = notp.totp.verify(token, user.twofa_secret, twofa_verification_opts);
if (verify && verify.delta == 0) {
if (user.twofa_last_token !== token && verify) {
callback({
ok: true,
valid: true,
@ -422,8 +491,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");
@ -499,6 +572,9 @@ exports.entryPage = "dashboard";
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.interval = monitor.interval;
bean.retryInterval = monitor.retryInterval;
bean.hostname = monitor.hostname;
@ -511,6 +587,7 @@ exports.entryPage = "dashboard";
bean.accepted_statuscodes_json = JSON.stringify(monitor.accepted_statuscodes);
bean.dns_resolve_type = monitor.dns_resolve_type;
bean.dns_resolve_server = monitor.dns_resolve_server;
bean.pushToken = monitor.pushToken;
await R.store(bean);
@ -577,6 +654,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 {
@ -638,6 +747,8 @@ exports.entryPage = "dashboard";
});
await sendMonitorList(socket);
// Clear heartbeat list on client
await sendImportantHeartbeatList(socket, monitorID, true, true);
} catch (e) {
callback({
@ -805,10 +916,14 @@ exports.entryPage = "dashboard";
try {
checkLogin(socket);
if (! password.currentPassword) {
if (! password.newPassword) {
throw new Error("Invalid new 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 R.findOne("user", " id = ? AND active = 1 ", [
socket.userID,
]);
@ -862,6 +977,8 @@ exports.entryPage = "dashboard";
msg: "Saved"
});
sendInfo(socket);
} catch (e) {
callback({
ok: false,
@ -1019,6 +1136,9 @@ exports.entryPage = "dashboard";
name: monitorListData[i].name,
type: monitorListData[i].type,
url: monitorListData[i].url,
method: monitorListData[i].method || "GET",
body: monitorListData[i].body,
headers: monitorListData[i].headers,
interval: monitorListData[i].interval,
retryInterval: retryInterval,
hostname: monitorListData[i].hostname,
@ -1034,6 +1154,10 @@ exports.entryPage = "dashboard";
notificationIDList: {},
};
if (monitorListData[i].pushToken) {
monitor.pushToken = monitorListData[i].pushToken;
}
let bean = R.dispense("monitor");
let notificationIDList = monitor.notificationIDList;
@ -1181,6 +1305,7 @@ exports.entryPage = "dashboard";
// Status Page Socket Handler for admin only
statusPageSocketHandler(socket);
databaseSocketHandler(socket);
debug("added all socket handlers");
@ -1214,8 +1339,14 @@ exports.entryPage = "dashboard";
}
startMonitors();
checkVersion.startInterval();
if (testMode) {
startUnitTest();
}
});
initBackgroundJobs(args);
})();
async function updateMonitorNotification(monitorID, notificationIDList) {
@ -1292,7 +1423,7 @@ async function initDatabase() {
fs.copyFileSync(Database.templatePath, Database.path);
}
console.log("Connecting to Database");
console.log("Connecting to the Database");
await Database.connect();
console.log("Connected");
@ -1392,7 +1523,7 @@ async function shutdownFunction(signal) {
}
function finalFunction() {
console.log("Graceful shutdown successfully!");
console.log("Graceful shutdown successful!");
}
gracefulShutdown(server, {
@ -1407,5 +1538,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");
});

37
server/socket-handlers/database-socket-handler.js

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

146
server/util-server.js

@ -5,6 +5,17 @@ const { debug } = 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);
/**
* Init or reset JWT secret
@ -115,7 +126,7 @@ exports.setting = async function (key) {
}
};
exports.setSetting = async function (key, value) {
exports.setSetting = async function (key, value, type = null) {
let bean = await R.findOne("setting", " `key` = ? ", [
key,
]);
@ -123,6 +134,7 @@ exports.setSetting = async function (key, value) {
bean = R.dispense("setting");
bean.key = key;
}
bean.type = type;
bean.value = JSON.stringify(value);
await R.store(bean);
};
@ -185,38 +197,57 @@ const getDaysRemaining = (validFrom, validTo) => {
return daysRemaining;
};
exports.checkCertificate = function (res) {
const {
valid_from,
valid_to,
subjectaltname,
issuer,
fingerprint,
} = res.request.res.socket.getPeerCertificate(false);
if (!valid_from || !valid_to || !subjectaltname) {
throw {
message: "No TLS certificate in response",
};
}
// Fix certificate Info for display
// param: info - the chain obtained from getPeerCertificate()
const parseCertificateInfo = function (info) {
let link = info;
let i = 0;
const valid = res.request.res.socket.authorized || false;
const existingList = {};
const validTo = new Date(valid_to);
while (link) {
debug(`[${i}] ${link.fingerprint}`);
if (!link.valid_from || !link.valid_to) {
break;
}
link.validTo = new Date(link.valid_to);
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.issuerCertificate.fingerprint in existingList) {
debug(`[Last] ${link.issuerCertificate.fingerprint}`);
link.issuerCertificate = null;
break;
} else {
link = link.issuerCertificate;
}
const validFor = subjectaltname
.replace(/DNS:|IP Address:/g, "")
.split(", ");
// Should be no use, but just in case.
if (i > 500) {
throw new Error("Dead loop occurred in parseCertificateInfo");
}
i++;
}
const daysRemaining = getDaysRemaining(new Date(), validTo);
return info;
};
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 {
valid,
validFor,
validTo,
daysRemaining,
issuer,
fingerprint,
valid: valid,
certInfo: parsedInfo
};
};
@ -272,16 +303,6 @@ exports.getTotalClientInRoom = (io, roomName) => {
}
};
exports.genSecret = () => {
let secret = "";
let chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
let charsLength = chars.length;
for ( let i = 0; i < 64; i++ ) {
secret += chars.charAt(Math.floor(Math.random() * charsLength));
}
return secret;
};
exports.allowDevAllOrigin = (res) => {
if (process.env.NODE_ENV === "development") {
exports.allowAllOrigin(res);
@ -298,3 +319,54 @@ exports.checkLogin = (socket) => {
throw new Error("You are not logged in.");
}
};
exports.startUnitTest = async () => {
console.log("Starting unit test...");
const npm = /^win/.test(process.platform) ? "npm.cmd" : "npm";
const child = child_process.spawn(npm, ["run", "jest"]);
child.stdout.on("data", (data) => {
console.log(data.toString());
});
child.stderr.on("data", (data) => {
console.log(data.toString());
});
child.on("close", function (code) {
console.log("Jest exit code: " + code);
process.exit(code);
});
};
/**
* @param body : Buffer
* @returns {string}
*/
exports.convertToUTF8 = (body) => {
const guessEncoding = chardet.detect(body);
//debug("Guess Encoding: " + guessEncoding);
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 (_) { }
};

9
src/App.vue

@ -1,7 +1,12 @@
<template>
<router-view />
<router-view />
</template>
<script>
export default {}
import { setPageLocale } from "./util-frontend";
export default {
created() {
setPageLocale();
},
};
</script>

46
src/assets/app.scss

@ -1,8 +1,9 @@
@import "vars.scss";
@import "multiselect.scss";
@import "node_modules/bootstrap/scss/bootstrap";
#app {
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, segoe ui, Roboto, helvetica neue, Arial, noto sans, sans-serif, apple color emoji, segoe ui emoji, segoe ui symbol, noto color emoji;
font-family: BlinkMacSystemFont, segoe ui, Roboto, helvetica neue, Arial, noto sans, sans-serif, apple color emoji, segoe ui emoji, segoe ui symbol, noto color emoji;
}
h1 {
@ -13,6 +14,10 @@ h2 {
font-size: 26px;
}
textarea.form-control {
border-radius: 19px;
}
::-webkit-scrollbar {
width: 10px;
}
@ -179,7 +184,12 @@ h2 {
border-color: $dark-border-color;
}
.table-hover > tbody > tr:hover {
.form-control:disabled, .form-control[readonly] {
background-color: #232f3b;
opacity: 1;
}
.table-hover > tbody > tr:hover > * {
--bs-table-accent-bg: #070a10;
color: $dark-font-color;
}
@ -233,30 +243,6 @@ h2 {
color: $dark-font-color;
}
// Multiselect
.multiselect__tags {
background-color: $dark-bg2;
border-color: $dark-border-color;
}
.multiselect__input, .multiselect__single {
background-color: $dark-bg2;
color: $dark-font-color;
}
.multiselect__content-wrapper {
background-color: $dark-bg2;
border-color: $dark-border-color;
}
.multiselect--above .multiselect__content-wrapper {
border-color: $dark-border-color;
}
.multiselect__option--selected {
background-color: $dark-bg;
}
.monitor-list {
.item {
&:hover {
@ -360,6 +346,10 @@ h2 {
&.active {
background-color: #cdf8f4;
}
.tags {
// Removes margin to line up tags list with uptime percentage
margin-left: -0.25rem;
}
}
}
@ -428,3 +418,7 @@ h2 {
.vue-image-crop-upload .vicp-wrap {
border-radius: 10px !important;
}
// Localization
@import "localization.scss";

5
src/assets/localization.scss

@ -0,0 +1,5 @@
html[lang='fa'] {
#app {
font-family: 'IRANSans', 'Iranian Sans','B Nazanin', 'Tahoma', ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, segoe ui, Roboto, helvetica neue, Arial, noto sans, sans-serif, apple color emoji, segoe ui emoji, segoe ui symbol, noto color emoji;
}
}

73
src/assets/multiselect.scss

@ -0,0 +1,73 @@
@import "vars.scss";
@import "node_modules/vue-multiselect/dist/vue-multiselect";
.multiselect__tags {
border-radius: 1.5rem;
border: 1px solid #ced4da;
min-height: 38px;
padding: 6px 40px 0 8px;
}
.multiselect--active .multiselect__tags {
border-radius: 1rem;
}
.multiselect__option--highlight {
background: $primary !important;
}
.multiselect__option--highlight::after {
background: $primary !important;
}
.multiselect__tag {
border-radius: $border-radius;
margin-bottom: 0;
padding: 6px 26px 6px 10px;
background: $primary !important;
}
.multiselect__placeholder {
font-size: 1rem;
padding-left: 6px;
padding-top: 0;
padding-bottom: 0;
margin-bottom: 0;
opacity: 0.67;
}
.multiselect__input,
.multiselect__single {
line-height: 14px;
margin-bottom: 0;
}
.dark {
.multiselect__tag {
color: $dark-font-color2;
}
.multiselect__tags {
background-color: $dark-bg2;
border-color: $dark-border-color;
}
.multiselect__input,
.multiselect__single {
background-color: $dark-bg2;
color: $dark-font-color;
}
.multiselect__content-wrapper {
background-color: $dark-bg2;
border-color: $dark-border-color;
}
.multiselect--above .multiselect__content-wrapper {
border-color: $dark-border-color;
}
.multiselect__option--selected {
background-color: $dark-bg;
}
}

52
src/components/CertificateInfo.vue

@ -0,0 +1,52 @@
<template>
<div>
<h4>{{ $t("Certificate Info") }}</h4>
{{ $t("Certificate Chain") }}:
<div
v-if="valid"
class="rounded d-inline-flex ms-2 text-white tag-valid"
>
{{ $t("Valid") }}
</div>
<div
v-if="!valid"
class="rounded d-inline-flex ms-2 text-white tag-invalid"
>
{{ $t("Invalid") }}
</div>
<certificate-info-row :cert="certInfo" />
</div>
</template>
<script>
import CertificateInfoRow from "./CertificateInfoRow.vue";
export default {
components: {
CertificateInfoRow,
},
props: {
certInfo: {
type: Object,
required: true,
},
valid: {
type: Boolean,
required: true,
},
},
};
</script>
<style lang="scss" scoped>
@import "../assets/vars.scss";
.tag-valid {
padding: 2px 25px;
background-color: $primary;
}
.tag-invalid {
padding: 2px 25px;
background-color: $danger;
}
</style>

122
src/components/CertificateInfoRow.vue

@ -0,0 +1,122 @@
<template>
<div>
<div class="d-flex flex-row align-items-center p-1 overflow-hidden">
<div class="m-3 ps-3">
<div class="cert-icon">
<font-awesome-icon icon="file" />
<font-awesome-icon class="award-icon" icon="award" />
</div>
</div>
<div class="m-3">
<table class="text-start">
<tbody>
<tr class="my-3">
<td class="px-3">Subject:</td>
<td>{{ formatSubject(cert.subject) }}</td>
</tr>
<tr class="my-3">
<td class="px-3">Valid To:</td>
<td><Datetime :value="cert.validTo" /></td>
</tr>
<tr class="my-3">
<td class="px-3">Days Remaining:</td>
<td>{{ cert.daysRemaining }}</td>
</tr>
<tr class="my-3">
<td class="px-3">Issuer:</td>
<td>{{ formatSubject(cert.issuer) }}</td>
</tr>
<tr class="my-3">
<td class="px-3">Fingerprint:</td>
<td>{{ cert.fingerprint }}</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="d-flex">
<font-awesome-icon
v-if="cert.issuerCertificate"
class="m-2 ps-6 link-icon"
icon="link"
/>
</div>
<certificate-info-row
v-if="cert.issuerCertificate"
:cert="cert.issuerCertificate"
/>
</div>
</template>
<script>
import Datetime from "../components/Datetime.vue";
export default {
name: "CertificateInfoRow",
components: {
Datetime,
},
props: {
cert: {
type: Object,
required: true,
},
},
methods: {
formatSubject(subject) {
if (subject.O && subject.CN && subject.C) {
return `${subject.CN} - ${subject.O} (${subject.C})`;
} else if (subject.O && subject.CN) {
return `${subject.CN} - ${subject.O}`;
} else if (subject.CN) {
return subject.CN;
} else {
return "no info";
}
},
},
};
</script>
<style lang="scss" scoped>
@import "../assets/vars.scss";
table {
overflow: hidden;
}
.cert-icon {
position: relative;
font-size: 70px;
color: $link-color;
opacity: 0.5;
.dark & {
color: $dark-font-color;
opacity: 0.3;
}
}
.award-icon {
position: absolute;
font-size: 0.5em;
bottom: 20%;
left: 12%;
color: white;
.dark & {
color: $dark-bg;
}
}
.link-icon {
font-size: 20px;
margin-left: 50px !important;
color: $link-color;
opacity: 0.5;
.dark & {
color: $dark-font-color;
opacity: 0.3;
}
}
</style>

122
src/components/CopyableInput.vue

@ -0,0 +1,122 @@
<template>
<div class="input-group">
<input
:id="id"
ref="input"
v-model="model"
:type="type"
class="form-control"
:placeholder="placeholder"
:autocomplete="autocomplete"
:required="required"
:readonly="readonly"
:disabled="disabled"
>
<a class="btn btn-outline-primary" @click="copyToClipboard(model)">
<font-awesome-icon :icon="icon" />
</a>
</div>
</template>
<script>
let timeout;
export default {
props: {
id: {
type: String,
default: ""
},
type: {
type: String,
default: "text"
},
modelValue: {
type: String,
default: ""
},
placeholder: {
type: String,
default: ""
},
autocomplete: {
type: String,
default: undefined,
},
required: {
type: Boolean
},
readonly: {
type: String,
default: undefined,
},
disabled: {
type: String,
default: undefined,
},
},
data() {
return {
visibility: "password",
icon: "copy",
};
},
computed: {
model: {
get() {
return this.modelValue;
},
set(value) {
this.$emit("update:modelValue", value);
}
}
},
created() {
},
methods: {
showInput() {
this.visibility = "text";
},
hideInput() {
this.visibility = "password";
},
copyToClipboard(textToCopy) {
this.icon = "check";
clearTimeout(timeout);
timeout = setTimeout(() => {
this.icon = "copy";
}, 3000);
// navigator clipboard api needs a secure context (https)
if (navigator.clipboard && window.isSecureContext) {
// navigator clipboard api method'
return navigator.clipboard.writeText(textToCopy);
} else {
// text area method
let textArea = document.createElement("textarea");
textArea.value = textToCopy;
// make the textarea out of viewport
textArea.style.position = "fixed";
textArea.style.left = "-999999px";
textArea.style.top = "-999999px";
document.body.appendChild(textArea);
textArea.focus();
textArea.select();
return new Promise((res, rej) => {
// here the magic happens
document.execCommand("copy") ? res() : rej();
textArea.remove();
});
}
}
}
};
</script>

4
src/components/HeartbeatBar.vue

@ -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}` : ``);
}
},
}
@ -186,7 +186,7 @@ export default {
.beat {
display: inline-block;
background-color: $primary;
border-radius: 50rem;
border-radius: $border-radius;
&.empty {
background-color: aliceblue;

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

Loading…
Cancel
Save