mirror of
https://github.com/louislam/uptime-kuma.git
synced 2025-09-10 20:57:19 +08:00
Compare commits
1137 Commits
1.18.3
...
1.22.0-bet
Author | SHA1 | Date | |
---|---|---|---|
|
71c34694b7 | ||
|
2128ed5ce3 | ||
|
09ab6a015b | ||
|
ec858eb67a | ||
|
c4c3fc81b2 | ||
|
8aa577529f | ||
|
cdd5067b17 | ||
|
5fdb01308a | ||
|
eb1a1d0ac7 | ||
|
a1fc283b3c | ||
|
419b684433 | ||
|
8bc139d8c1 | ||
|
91dfd8dfaa | ||
|
1f405cf2a0 | ||
|
cf61077dd8 | ||
|
4396e0d4d8 | ||
|
240db1d173 | ||
|
ef06b5376d | ||
|
186d733134 | ||
|
225ba61e22 | ||
|
11b32ce553 | ||
|
3707919025 | ||
|
d10e378fb1 | ||
|
370328c3b8 | ||
|
1d8a82ae3e | ||
|
7da336e975 | ||
|
b8c12cca2a | ||
|
88fcfcc6fc | ||
|
fcb22f7d05 | ||
|
5be41990bc | ||
|
09149f50e0 | ||
|
8f60274582 | ||
|
7dadac3ebe | ||
|
28c29f755d | ||
|
b426840b5b | ||
|
4f2d39d5fc | ||
|
4012fc6964 | ||
|
d1b52bc098 | ||
|
c9a32f9dbb | ||
|
b884f82de6 | ||
|
65928a26c7 | ||
|
abe00efa7f | ||
|
0c364fc288 | ||
|
0d21529037 | ||
|
fea8ef8367 | ||
|
37031fb9a7 | ||
|
58ec53fb1d | ||
|
57190b58c6 | ||
|
6c2948d2de | ||
|
68f389868c | ||
|
af5d7cbb0b | ||
|
56f448bfe5 | ||
|
2b46da0f47 | ||
|
9bd76c2795 | ||
|
4b3a2ee71b | ||
|
1634df5a39 | ||
|
039fdb0730 | ||
|
20af2d9d95 | ||
|
04806ba4f3 | ||
|
3ff910a8f8 | ||
|
343a1d3344 | ||
|
f1c184c30c | ||
|
f3c09f2bbd | ||
|
85eb084305 | ||
|
0735f12d19 | ||
|
8ed2b59410 | ||
|
0b8dddba24 | ||
|
2114295381 | ||
|
bc95875aa0 | ||
|
c1efe0f26d | ||
|
a0d0d5b015 | ||
|
8d05d80a5f | ||
|
36942de329 | ||
|
771ca09331 | ||
|
3cb287a40e | ||
|
83a59bd984 | ||
|
446b5fa9e4 | ||
|
0d1b5321ad | ||
|
1e1cc86a10 | ||
|
9dc02bb8e2 | ||
|
bb15fa0179 | ||
|
966066b897 | ||
|
8d24891b8e | ||
|
ba7de3fd37 | ||
|
80c8fd7372 | ||
|
a27386bb92 | ||
|
ce70b3fc62 | ||
|
06fba5b55a | ||
|
f2c294e9e5 | ||
|
332e54937e | ||
|
a1adc30a89 | ||
|
6ce882ad4a | ||
|
e392d12585 | ||
|
253214ad2b | ||
|
33de7bdb1c | ||
|
7f5d0e5490 | ||
|
1a344c1371 | ||
|
28b0f8fc00 | ||
|
0eaaa8b6fa | ||
|
5cd506e340 | ||
|
f0beccf6bf | ||
|
72c16c3aa2 | ||
|
aa8454b73f | ||
|
d23cb0b382 | ||
|
9975050872 | ||
|
f8c2909576 | ||
|
fcfe13e52d | ||
|
9f51115a19 | ||
|
4057ca6e72 | ||
|
8a3bce44ef | ||
|
dfe6f52f6a | ||
|
333a631389 | ||
|
eaa948579b | ||
|
74dd07c3ca | ||
|
f75cf3a186 | ||
|
a3e31b22bc | ||
|
078d1f96a5 | ||
|
8207f16396 | ||
|
ba82abe5f3 | ||
|
eb9c748071 | ||
|
3579520575 | ||
|
030faddd1c | ||
|
0e516a42e5 | ||
|
680dccefea | ||
|
8c9423f4de | ||
|
f433f33418 | ||
|
d4a31cf02a | ||
|
a7588adc52 | ||
|
6356b1e50a | ||
|
af6e01ee3a | ||
|
11f4cb8725 | ||
|
1bf97e701d | ||
|
4c1ac5e870 | ||
|
9e320dc5fb | ||
|
2f3f929fbd | ||
|
b776e88b26 | ||
|
49741bbef2 | ||
|
1f7f1f70bf | ||
|
be7d3f6142 | ||
|
7706c29564 | ||
|
9dd1b1ca0f | ||
|
21ad715e6a | ||
|
23af66f618 | ||
|
03aa685d3f | ||
|
84c1baf706 | ||
|
23808efe2a | ||
|
1db25a329f | ||
|
e314d517ad | ||
|
84d1cb73b6 | ||
|
ddd3d3bc92 | ||
|
190e85d2c8 | ||
|
d8511fa201 | ||
|
e76d29dee5 | ||
|
fb38048159 | ||
|
80f1959871 | ||
|
4ddc3b5f5e | ||
|
d173a3c663 | ||
|
45ef7b2f69 | ||
|
6b078b83bd | ||
|
22f730499f | ||
|
1be74e2720 | ||
|
32f84b5e4e | ||
|
97c7ad9cc7 | ||
|
8f449ab738 | ||
|
dbfaddafca | ||
|
511038b45a | ||
|
e8d48561fc | ||
|
aeb2feacd3 | ||
|
c01055efb3 | ||
|
17ae47d091 | ||
|
de0d1edfd4 | ||
|
0f5a243450 | ||
|
b975c24531 | ||
|
524cf7c607 | ||
|
a6acd065bb | ||
|
227cec86a8 | ||
|
ba52e1c885 | ||
|
02291730fe | ||
|
dcc065c86f | ||
|
501dc29e6d | ||
|
d2b09ef042 | ||
|
f608590526 | ||
|
a7f21bffec | ||
|
ebd42444d1 | ||
|
f0f7645c57 | ||
|
3ada6fa99b | ||
|
4fb10a4e3f | ||
|
1aac15bccc | ||
|
cbcd2a1027 | ||
|
17f038767d | ||
|
edd1c6b662 | ||
|
29be8d3ddb | ||
|
2ac1abd424 | ||
|
738a494dcb | ||
|
e575d41f7d | ||
|
8ee4b844fd | ||
|
4ae437dd61 | ||
|
6cb296b07a | ||
|
644c6a872f | ||
|
8c69c18f6d | ||
|
c1a1160767 | ||
|
a0ebd88849 | ||
|
2e9413cf33 | ||
|
278b52ec34 | ||
|
caa757a27c | ||
|
3ce117a943 | ||
|
cefe484b47 | ||
|
a700892709 | ||
|
13d721ccf8 | ||
|
6c66bff518 | ||
|
bea51d048b | ||
|
2e1a0fe4d5 | ||
|
27b0895722 | ||
|
e687698851 | ||
|
fc4312ca1a | ||
|
fbdeb30ce7 | ||
|
41bda4e1d7 | ||
|
4869e6531c | ||
|
302b9cf644 | ||
|
3c3a192943 | ||
|
b64c835cee | ||
|
5266e713e6 | ||
|
86579d245f | ||
|
b6169408be | ||
|
4f05912276 | ||
|
bf525371d9 | ||
|
89bfc3bf33 | ||
|
a2014278b8 | ||
|
70572af1af | ||
|
b31c23a43b | ||
|
f4ee5271af | ||
|
7330db3563 | ||
|
097567e5f0 | ||
|
35f300c8eb | ||
|
4c9d7ac8ca | ||
|
ca52047bf5 | ||
|
d9558833fc | ||
|
776a482a1d | ||
|
d2527d7254 | ||
|
6dfca0c163 | ||
|
df47609671 | ||
|
e63f7562f8 | ||
|
8921ed0cff | ||
|
35a56dd9e0 | ||
|
442f54de84 | ||
|
72317633d9 | ||
|
1973db28bf | ||
|
972ae60cfc | ||
|
56410afc3b | ||
|
df975c2750 | ||
|
0cfd3fa642 | ||
|
0b91391ced | ||
|
02fde56aec | ||
|
da225a225f | ||
|
a40c03f6f3 | ||
|
c238508060 | ||
|
abcbc3c55e | ||
|
350451edc8 | ||
|
b4b2ae55c0 | ||
|
9ecb890f56 | ||
|
4329fc6751 | ||
|
836e125256 | ||
|
0fe98de256 | ||
|
3825dd9f42 | ||
|
c6cd0d9312 | ||
|
82975f8d7b | ||
|
2ebbcc25a9 | ||
|
f2323b012b | ||
|
e5a6238cde | ||
|
61506b1af2 | ||
|
dbe73bd6ae | ||
|
0778549a6d | ||
|
09fa60de55 | ||
|
1e80365b73 | ||
|
491239415e | ||
|
cf59832d51 | ||
|
8f259e1756 | ||
|
29b2809279 | ||
|
16f2701f61 | ||
|
3bbf269da0 | ||
|
56d716cee4 | ||
|
0efabb4e39 | ||
|
b7aad4677d | ||
|
0a9ebf7fa4 | ||
|
09aa7be4f5 | ||
|
d3a3dcbde2 | ||
|
7447a6570f | ||
|
66e80aa6c3 | ||
|
f1c3604ab0 | ||
|
e8a81379bb | ||
|
1f13db26cc | ||
|
1777270bb7 | ||
|
062191d61f | ||
|
86e9c8ade9 | ||
|
35cf31ced1 | ||
|
4608560b1d | ||
|
6a37a2a05b | ||
|
ba7af9b569 | ||
|
6635412980 | ||
|
6e0aa109bc | ||
|
533bc1505b | ||
|
391692a708 | ||
|
a599f5149b | ||
|
f32fcb204f | ||
|
230de63460 | ||
|
e8814e8479 | ||
|
2dedc1cfbd | ||
|
65933de0cd | ||
|
ce8eebc838 | ||
|
ae5a683af8 | ||
|
70bb69fc73 | ||
|
2702335d91 | ||
|
6de57f3283 | ||
|
0dce492226 | ||
|
3e60912992 | ||
|
f17d23f5d8 | ||
|
306cd37ec0 | ||
|
26dfa248c5 | ||
|
993bdb3550 | ||
|
6335b72c2b | ||
|
7ec09d0118 | ||
|
92c9b8bb63 | ||
|
86a8ec27f3 | ||
|
010c7d681f | ||
|
1d5246c34b | ||
|
8d1847c032 | ||
|
13387cdd90 | ||
|
44077b38cb | ||
|
b60c11ff28 | ||
|
36aef2ae0d | ||
|
db5641c4bf | ||
|
901795729b | ||
|
7c1cca38dc | ||
|
ce767d2350 | ||
|
b433a8fe5a | ||
|
9fb190cc3c | ||
|
917f406f30 | ||
|
06ec7f2b61 | ||
|
6af1306b89 | ||
|
7bbfb640ba | ||
|
9758e1b71a | ||
|
eed49fed59 | ||
|
1d7883208a | ||
|
5005c56e51 | ||
|
cd61b28c85 | ||
|
8b1affb9f4 | ||
|
a838432aef | ||
|
1718890be7 | ||
|
efd4dece1b | ||
|
c8a830047b | ||
|
db85f1758a | ||
|
f02e8be3e2 | ||
|
40cd5d41e3 | ||
|
f2dc27c8fa | ||
|
2bb3b634c0 | ||
|
3f3c5dca9f | ||
|
482d2657ca | ||
|
fa2221781e | ||
|
3e97563ee4 | ||
|
68ea65bbd9 | ||
|
6cea6dc001 | ||
|
a990dc89d8 | ||
|
16ce1f9ddf | ||
|
2f5d9e7e46 | ||
|
f75ff2da98 | ||
|
d1808fe9a3 | ||
|
22577b88e9 | ||
|
61699bb238 | ||
|
8d31187a74 | ||
|
9a1301d0a9 | ||
|
5f1b58e836 | ||
|
193a273557 | ||
|
bc87abf5c2 | ||
|
150607cc93 | ||
|
ad26f0e817 | ||
|
cbbd3e20ad | ||
|
beb22f743d | ||
|
6fc34e44d9 | ||
|
94c3861608 | ||
|
71c800b880 | ||
|
7b4f90ce92 | ||
|
0986457017 | ||
|
b21c2adcc2 | ||
|
beafbf27ad | ||
|
958354e4db | ||
|
38ab5e0f3e | ||
|
7e178d93df | ||
|
97e276bdb5 | ||
|
fc8a324f41 | ||
|
bba8c6fe4e | ||
|
fee8fd9320 | ||
|
669f8700b2 | ||
|
11fa690e09 | ||
|
06ee68dc0e | ||
|
42a69c16ca | ||
|
db6b863445 | ||
|
b91c526d2e | ||
|
5b0b743f81 | ||
|
3c5f998191 | ||
|
a80f228136 | ||
|
ea3b3abe36 | ||
|
48c6f0578c | ||
|
7f9332c753 | ||
|
d668812df1 | ||
|
f32d3af62c | ||
|
8a115670cd | ||
|
a7b49fcd98 | ||
|
487eae71c7 | ||
|
4fed0c152e | ||
|
43c797a34e | ||
|
f9a6d7ec44 | ||
|
4a5a424198 | ||
|
f47f2d5c87 | ||
|
54cd7a0402 | ||
|
f0ae67f89a | ||
|
98bb854832 | ||
|
46894793fc | ||
|
ef64077980 | ||
|
e873fea86d | ||
|
c4a9374671 | ||
|
c65a920050 | ||
|
7b8ed01f27 | ||
|
cecb0b6425 | ||
|
8e3dd4202f | ||
|
af82ea742c | ||
|
2fa233ae7f | ||
|
e9475ed3c0 | ||
|
b923ba72ca | ||
|
06278dc51f | ||
|
10228874fa | ||
|
c5034c8f38 | ||
|
186ca30508 | ||
|
896e33815d | ||
|
7e3734af53 | ||
|
5789112f55 | ||
|
4dfc1a0221 | ||
|
6235ce6b29 | ||
|
81a829bda7 | ||
|
fa7f75a930 | ||
|
7c8cff7708 | ||
|
5e1489a6ed | ||
|
df5da0054e | ||
|
7da48b27a5 | ||
|
de7df46aa8 | ||
|
9ccaa4d120 | ||
|
42033c692f | ||
|
aad1a7e0fa | ||
|
2c62d197a0 | ||
|
dad21065cf | ||
|
02ddb58686 | ||
|
c061455475 | ||
|
0e38391454 | ||
|
22902139d2 | ||
|
4642f9be0a | ||
|
372c6b9078 | ||
|
0be8b111e2 | ||
|
cf32b8bc64 | ||
|
6a98ffe2f6 | ||
|
72106ba4c4 | ||
|
3ab0faee91 | ||
|
9e3a77f419 | ||
|
d1c43f432a | ||
|
ba3c7210ab | ||
|
3b74f5f359 | ||
|
c9b4a7f53e | ||
|
bd2c1d9c34 | ||
|
cef0a0faf4 | ||
|
4df8db3f54 | ||
|
dfb95dfdcb | ||
|
b8720b46c3 | ||
|
1d4af39820 | ||
|
dd1d71530f | ||
|
01c71a0242 | ||
|
d553c4c4f7 | ||
|
e7feca1cd6 | ||
|
cd796898d0 | ||
|
05443f9bb7 | ||
|
d7f2fa982a | ||
|
ee2eb5109b | ||
|
fdc3b2d57a | ||
|
e04a8203dc | ||
|
2620ec3fae | ||
|
1877b90b3a | ||
|
609a61e600 | ||
|
32ddff4e64 | ||
|
11b45dd274 | ||
|
fb2f7179e9 | ||
|
e3573ced65 | ||
|
8afc55db4e | ||
|
31fa074ffc | ||
|
379d54e520 | ||
|
a518188e6f | ||
|
7d363ea146 | ||
|
d1175ff471 | ||
|
aad087caac | ||
|
0d6a8b2101 | ||
|
cd18b96f69 | ||
|
c19dcdba44 | ||
|
d9316f43ac | ||
|
33bb9f1ade | ||
|
6048bc5dfc | ||
|
5bf00fbe0b | ||
|
76bdb62a5b | ||
|
99eebf18b8 | ||
|
1bcca60574 | ||
|
aeea1ff03f | ||
|
b49e3b65c1 | ||
|
e9876986eb | ||
|
9268ad2f2c | ||
|
f1aa567a50 | ||
|
9d53db1504 | ||
|
a6f68a2e06 | ||
|
2ef98c1b10 | ||
|
fea33a6475 | ||
|
3816c696cd | ||
|
51860261e9 | ||
|
d8a517e843 | ||
|
971977b295 | ||
|
36c32c3636 | ||
|
49396e2ccc | ||
|
90d6fbd20b | ||
|
db918be126 | ||
|
5f71515253 | ||
|
bcac18cc2b | ||
|
06d1309d78 | ||
|
fe66a24f00 | ||
|
cb563950e5 | ||
|
b9dd04ab05 | ||
|
c70ccd1183 | ||
|
25d50e7660 | ||
|
050c388bc3 | ||
|
34b1169ad6 | ||
|
6bda8d0b55 | ||
|
0b9c5c70b2 | ||
|
73f85f4861 | ||
|
48d89d8e61 | ||
|
727de9838b | ||
|
a16ea4c6f3 | ||
|
46413a57e8 | ||
|
e10ea1049d | ||
|
7984a52929 | ||
|
8f78c54ca2 | ||
|
c5ff010669 | ||
|
29976d8a03 | ||
|
f1bac7ce8a | ||
|
51dbb23230 | ||
|
8092640e20 | ||
|
19c8538149 | ||
|
c30e88ece2 | ||
|
cb953361b1 | ||
|
c12b06348b | ||
|
48b637d4c8 | ||
|
3439074835 | ||
|
36d160ad02 | ||
|
3a361d2621 | ||
|
53d9e98e47 | ||
|
8725e5daf9 | ||
|
d45aee450d | ||
|
727acb32bf | ||
|
e241728419 | ||
|
43941fa2c6 | ||
|
faa78443d6 | ||
|
ab3b2bddba | ||
|
e1f956879d | ||
|
1c0174c319 | ||
|
ef54d9e3b6 | ||
|
0197778af1 | ||
|
271cca0d23 | ||
|
39c99b0ec4 | ||
|
b6d7af988d | ||
|
5fff63cd59 | ||
|
c42e550382 | ||
|
4323dee781 | ||
|
1bfb290718 | ||
|
06dc61b343 | ||
|
afadfe32d5 | ||
|
5f2affb38c | ||
|
10c6f3b688 | ||
|
666838f334 | ||
|
a823ed8ccc | ||
|
121ab82cc3 | ||
|
e0f0178644 | ||
|
ec78d2a39b | ||
|
ff09276de2 | ||
|
e631db89b8 | ||
|
d39508a007 | ||
|
b0673ba9ce | ||
|
2a6d98ff01 | ||
|
f153082184 | ||
|
913bb611d5 | ||
|
3afe8013ca | ||
|
5a94a3fe3c | ||
|
c08d8a5eaf | ||
|
3ff0cbe311 | ||
|
fb2999706c | ||
|
2b3a3895b3 | ||
|
99c0b8cb71 | ||
|
29e24e0de9 | ||
|
3819266fa8 | ||
|
e10ba9ed7e | ||
|
603b3a7fb6 | ||
|
b2ddb5c9eb | ||
|
4287f7e885 | ||
|
d0ac3fc207 | ||
|
2b1cb66ff7 | ||
|
99d4b8ba50 | ||
|
e1021ba38a | ||
|
c2ca60aaa2 | ||
|
30fb5f0b7b | ||
|
a8af763f23 | ||
|
47dd7ef432 | ||
|
3bc9c19e7f | ||
|
c1461fd01f | ||
|
1aaf251840 | ||
|
3208d98738 | ||
|
fef09d3abd | ||
|
1a601c9377 | ||
|
15ec8db48c | ||
|
9446c2d102 | ||
|
2c581ade90 | ||
|
f286386f59 | ||
|
9286dcb6ce | ||
|
89465e6768 | ||
|
683f446cf5 | ||
|
a8f0f1d872 | ||
|
f82d7b4007 | ||
|
806eb799c1 | ||
|
c699868bb9 | ||
|
95c934e08b | ||
|
348d0170fa | ||
|
7dacc6a002 | ||
|
a6894d36f2 | ||
|
ce82ad1c12 | ||
|
4fb43034cd | ||
|
18ae6fa6c1 | ||
|
664da4a317 | ||
|
064bc00f46 | ||
|
fca0198d35 | ||
|
6828d337ae | ||
|
66573934f6 | ||
|
c444d78706 | ||
|
661fa87134 | ||
|
d48eb24046 | ||
|
aee4c22dee | ||
|
9a46b50989 | ||
|
faf3488b1e | ||
|
f3ac351d75 | ||
|
35bd129d66 | ||
|
aba515e172 | ||
|
ddce8f0cb0 | ||
|
1dc2546a39 | ||
|
98f5bc51a8 | ||
|
97bd306a09 | ||
|
645fd94bba | ||
|
82f875beca | ||
|
50573e6c89 | ||
|
e5ca67d062 | ||
|
54e63f3e25 | ||
|
2673b509a5 | ||
|
9329ec9234 | ||
|
d99d37898e | ||
|
dcda520d59 | ||
|
8816be24d8 | ||
|
e3828f09a3 | ||
|
5050ebc249 | ||
|
82f33f4445 | ||
|
a9b7bbcd9e | ||
|
edd5592dca | ||
|
e3246a2705 | ||
|
e1e6227245 | ||
|
b0e1bb4057 | ||
|
2a86b43d69 | ||
|
1dabbd6442 | ||
|
9cc3bd0de4 | ||
|
c4c720027c | ||
|
3eab6e8238 | ||
|
e637fa4e40 | ||
|
83e0401dd8 | ||
|
aab04f6644 | ||
|
2408b1bffa | ||
|
f155ec9ba8 | ||
|
417efd9711 | ||
|
7866d1ec12 | ||
|
bf5ac3fc19 | ||
|
434bff6714 | ||
|
1d64b0c11b | ||
|
f125534829 | ||
|
ea83af3404 | ||
|
b05c620ec2 | ||
|
9c4b03aee2 | ||
|
912686a299 | ||
|
ae0ef79060 | ||
|
a2045b59bb | ||
|
8ade7120ff | ||
|
e9044ae956 | ||
|
0c86a4d4e7 | ||
|
5683896910 | ||
|
b288d71535 | ||
|
861ddc6117 | ||
|
1197f881a1 | ||
|
211cbd4687 | ||
|
cd017fce98 | ||
|
dd6a76c4cc | ||
|
fa23e7ad19 | ||
|
aef85078eb | ||
|
86ba6f829e | ||
|
6ccf741bc4 | ||
|
0a58069742 | ||
|
316599210d | ||
|
2b57b3e863 | ||
|
6cd6a2edf0 | ||
|
86bcb85e9b | ||
|
6961b1bdd2 | ||
|
54d4c4d3f7 | ||
|
c47b6c5995 | ||
|
7ef404ccc1 | ||
|
a5ff27da7a | ||
|
7bb12a7e00 | ||
|
27585d0812 | ||
|
e675316635 | ||
|
b073ec2287 | ||
|
31f45dcfc9 | ||
|
49ac71e25c | ||
|
8128c8081b | ||
|
e99652c5a2 | ||
|
ceb7d48118 | ||
|
bfa32f6b07 | ||
|
ae3a543b3b | ||
|
1a9b013fc2 | ||
|
1326761a8a | ||
|
e48a987b9c | ||
|
cf21aa3737 | ||
|
9890a0754b | ||
|
15c64d458b | ||
|
be850dd596 | ||
|
3adc9e65d6 | ||
|
712a3c29d4 | ||
|
e9497ac1ab | ||
|
6437ef198f | ||
|
973d5692d0 | ||
|
468cb004d6 | ||
|
f7d41a30fa | ||
|
0ef686ac2f | ||
|
3b5893ea60 | ||
|
21cd4d64c3 | ||
|
db757123ba | ||
|
4dcf31621e | ||
|
71f00b3690 | ||
|
e427c70fef | ||
|
27e0b1eea1 | ||
|
9c1ba97e7d | ||
|
e9564619f1 | ||
|
521356e38a | ||
|
b91fe9d96d | ||
|
a21a47de93 | ||
|
f6d0f28b3a | ||
|
8433bceb32 | ||
|
98d001b38b | ||
|
0ed3dd5e4f | ||
|
d9f12a6376 | ||
|
56ba133a1f | ||
|
ec30147a7f | ||
|
2bc165379a | ||
|
2172112144 | ||
|
ecd661c801 | ||
|
8fab7112a1 | ||
|
cc4ed308b0 | ||
|
362280af14 | ||
|
21b418230c | ||
|
636fc8fcfc | ||
|
1c05ba09dc | ||
|
1565da87cf | ||
|
66d5408aad | ||
|
890e3abf58 | ||
|
6e50784b6b | ||
|
33355c51b7 | ||
|
5f5c2d7c46 | ||
|
71f4ab0aa6 | ||
|
24d1dd4c34 | ||
|
c00abac834 | ||
|
439f963749 | ||
|
f15c6470af | ||
|
80f2d6e2a7 | ||
|
852a088529 | ||
|
6bc0bd84af | ||
|
32f7a0084a | ||
|
f8658d6160 | ||
|
dd82f36da3 | ||
|
774d754b21 | ||
|
d596f8f7eb | ||
|
23a525e36a | ||
|
221d1d40f5 | ||
|
60ec87941e | ||
|
e8e4361e09 | ||
|
fe03170540 | ||
|
d6c91869af | ||
|
5d6770c0db | ||
|
7a13b959a3 | ||
|
675806829c | ||
|
21c1921867 | ||
|
e490ec6d29 | ||
|
7ad4392529 | ||
|
f3d3e064f8 | ||
|
80c91b8234 | ||
|
dc8289df12 | ||
|
caff9ca736 | ||
|
c7eb72e73b | ||
|
fc5ec5f492 | ||
|
40ebc2df79 | ||
|
abf5e435fe | ||
|
8a372201f1 | ||
|
8ec240fe19 | ||
|
5362aab0e5 | ||
|
bc7271b99c | ||
|
4ebf5a5f07 | ||
|
fbceefec36 | ||
|
0b959514f8 | ||
|
4c5e2ea237 | ||
|
7d92351568 | ||
|
494c53971c | ||
|
fc1914bccd | ||
|
4239cf4255 | ||
|
c196c34840 | ||
|
554402b484 | ||
|
b049e4e1b4 | ||
|
90a2668272 | ||
|
49b5de7d40 | ||
|
69e1880cd3 | ||
|
610b6248aa | ||
|
e591647b60 | ||
|
da99833d4c | ||
|
4bf23fdd1a | ||
|
f99a64da67 | ||
|
2e022789f6 | ||
|
73835f3328 | ||
|
4819112e25 | ||
|
2d3fd738e4 | ||
|
edd8fe2e22 | ||
|
204792dd2d | ||
|
942b55ca03 | ||
|
b8e8c1b9db | ||
|
0cead83705 | ||
|
7b0093b8b3 | ||
|
239910a27c | ||
|
cd7e362b81 | ||
|
a8af2a418e | ||
|
39ac9b887e | ||
|
28c3291020 | ||
|
50711391d1 | ||
|
e88e10cc8e | ||
|
27146ffeef | ||
|
41a9f2ff8a | ||
|
cd7a6e4019 | ||
|
8bb064c6fa | ||
|
1006fbd873 | ||
|
5554432b31 | ||
|
d111db0321 | ||
|
4147a4c404 | ||
|
8c684e9293 | ||
|
f025de6eaf | ||
|
9404efd86d | ||
|
7f394d0630 | ||
|
9fe9e235ca | ||
|
4d0bdae6bf | ||
|
608e3f5582 | ||
|
50b84f5f45 | ||
|
c60b741406 | ||
|
f6ea1fe9a5 | ||
|
b7e3ec2372 | ||
|
625fd7c2aa | ||
|
aec80b53d5 | ||
|
06852bbf0d | ||
|
056d957c1e | ||
|
e12225e595 | ||
|
1b6c587cc9 | ||
|
4a1db336df | ||
|
9e9c5cd1d2 | ||
|
1e689d99b4 | ||
|
14fffcf06b | ||
|
6962e056ce | ||
|
b7a898326e | ||
|
a89be0e6d4 | ||
|
b3ac7c3d43 | ||
|
c79b2913a2 | ||
|
b58b83541b | ||
|
df4f91c20d | ||
|
fc6b040a4e | ||
|
9838f36b50 | ||
|
a60072adbc | ||
|
e7aeb6f6bf | ||
|
06e570c52d | ||
|
890b8f8333 | ||
|
df21f7da76 | ||
|
3ea6711053 | ||
|
20c37a70f7 | ||
|
b75db27658 | ||
|
765d8e1297 | ||
|
9dc2cc1f0d | ||
|
2532becf61 | ||
|
6154776b34 | ||
|
7e6b92203d | ||
|
1da00d19fd | ||
|
da4bdab4f6 | ||
|
86ab97ef56 | ||
|
345b0c1829 | ||
|
2ac87fcea7 | ||
|
4862bec965 | ||
|
aa784fb3b2 | ||
|
466b403a96 | ||
|
f32441e2f6 | ||
|
39987ba9ac | ||
|
3b87209e26 | ||
|
e6dc0a0293 | ||
|
5c5a339a36 | ||
|
3040bd41d9 | ||
|
3b58fd3b3c | ||
|
bc86f8bb5f | ||
|
ab5f6dc82c | ||
|
5176fd02c1 | ||
|
02b5cae577 | ||
|
54aa7d5dca | ||
|
4cd5b5563f | ||
|
f1c30204b6 | ||
|
9da28fbbc7 | ||
|
851a04b082 | ||
|
68bc7ac421 | ||
|
73bfdb9ef9 | ||
|
e478084ff9 | ||
|
2dff7dd380 | ||
|
9bfa43100b | ||
|
ad5e1957b1 | ||
|
cc68ebca39 | ||
|
aa27d976c2 | ||
|
ecbc0f0477 | ||
|
f8c7da7995 | ||
|
f3660a0cec | ||
|
92caec95fe | ||
|
2c3abdc146 | ||
|
b1170211b7 | ||
|
eadf2c810a | ||
|
8aa97635ec | ||
|
ee1a56caae | ||
|
e886df4788 | ||
|
5196abfd36 | ||
|
3e68cf2a1c | ||
|
0ab82e6de3 | ||
|
8cdbe37f6f | ||
|
da16796ec4 | ||
|
28d13e198c | ||
|
14a062804e | ||
|
cf3e03ab40 | ||
|
191f3ad53b | ||
|
370d522920 | ||
|
e0a1ad8a1c | ||
|
9720006934 | ||
|
cd270bd8b5 | ||
|
bc3229828e | ||
|
a5f23b9839 | ||
|
2052fa175f | ||
|
15b63c82c3 | ||
|
b053bc61ce | ||
|
0e30843a75 | ||
|
2103edb604 | ||
|
b059a36e66 | ||
|
258ff56962 | ||
|
cb4e512dc6 | ||
|
4042c26390 | ||
|
5da2315534 | ||
|
204015f1f5 | ||
|
cc6d17d2e0 | ||
|
68862c0b3f | ||
|
fd15e7c2dc | ||
|
5c4cf68937 | ||
|
214ddc264d | ||
|
2ea71839d1 | ||
|
54efde8185 | ||
|
705124d4ac | ||
|
1cb6940590 | ||
|
0f8ad288f3 | ||
|
434174d350 | ||
|
3d1237ed53 | ||
|
b459408b10 | ||
|
f04fe4d230 | ||
|
e579610426 | ||
|
b115d3f8b9 | ||
|
134b3b8ac1 | ||
|
e7e7751e7b | ||
|
5cd58e6fa3 | ||
|
08763b700a | ||
|
781f855921 | ||
|
4339ca7eb5 | ||
|
9e81fe120f | ||
|
7313aa6563 | ||
|
c7871427c3 | ||
|
c0e67b6de9 | ||
|
a17084f75d | ||
|
e4fe7b802a | ||
|
5761bc9b90 | ||
|
92ea019fd4 | ||
|
a774b37369 | ||
|
06755f249d | ||
|
64f84eb118 | ||
|
afe12ccf24 | ||
|
6f4424de28 | ||
|
24cb212a37 | ||
|
d8a676abb6 | ||
|
0b8d4cdaac | ||
|
268cbdbf8d | ||
|
b60dde0b2d | ||
|
aecf95864e | ||
|
c662d259b0 | ||
|
6b47ad07ca | ||
|
f459ea845c | ||
|
b24c75eec5 | ||
|
cb5f90aa89 | ||
|
edacff123b | ||
|
2faf866e9e | ||
|
8cc3e4b7c1 | ||
|
7b9766091e | ||
|
d95e722658 | ||
|
39b6725163 | ||
|
dfb75c8afb | ||
|
e07aa982c3 | ||
|
1e8a16504b | ||
|
180d881ac1 | ||
|
2271ac4a5a | ||
|
f6bbd1ca67 | ||
|
2ee8378814 | ||
|
d5c02fc627 | ||
|
c84de4d259 | ||
|
c1ccaa7a9f | ||
|
539683f8e9 | ||
|
bd42450e55 | ||
|
71af23cf00 | ||
|
a577fba848 | ||
|
a36f24d827 | ||
|
b007681e67 | ||
|
07f9aafd7b | ||
|
1c8631af8d | ||
|
a487347b33 | ||
|
0d2e02f569 | ||
|
ad1a7c255f | ||
|
655ba015a0 | ||
|
230e5110b1 | ||
|
3eaccb560e | ||
|
f67d7cdf3f | ||
|
df2f536845 | ||
|
59e7aa74a3 | ||
|
43c1ec640c | ||
|
4f6dec41c6 | ||
|
e741e7582d | ||
|
4c45654780 | ||
|
e29527e22f | ||
|
91f9e10c94 | ||
|
7d3cc002ea | ||
|
6e07ed2081 | ||
|
da778f05ac | ||
|
4a7e96f9ea | ||
|
b879428a03 | ||
|
3c5de1c889 | ||
|
3e699f8ac3 | ||
|
b0d6b5b13d | ||
|
a42f7416b5 | ||
|
f9be918246 | ||
|
314ae38f91 | ||
|
c03d911657 | ||
|
e12642cf21 | ||
|
618d904001 | ||
|
6f86236b63 | ||
|
204339fbed | ||
|
b1465c0282 | ||
|
4002b9f577 | ||
|
f6919aef1d | ||
|
6537f4fe74 | ||
|
5809088f27 | ||
|
0814d643c1 | ||
|
3f63cb246b | ||
|
f11dfc8f43 | ||
|
9d99c39f30 | ||
|
617ba49e6c | ||
|
7853c2cc38 | ||
|
f61c1c47aa | ||
|
9fe07742ea | ||
|
a29eae3213 | ||
|
80698a58b8 | ||
|
bb883e6fa0 | ||
|
120e578398 | ||
|
7017c2e625 | ||
|
2f67d26702 | ||
|
90761cf831 | ||
|
7aa3f6c559 | ||
|
db4b2cd984 | ||
|
b4f0f8bca5 | ||
|
b382064384 | ||
|
68875c3091 | ||
|
f35d7c0a1a | ||
|
742b1337be | ||
|
fa1fc0fb05 | ||
|
3a90d246a4 | ||
|
6ec6410808 | ||
|
c3d655afb4 | ||
|
6bb79597e8 | ||
|
31cc328839 | ||
|
0d58526f25 | ||
|
2b9bf095a6 | ||
|
3a18801722 | ||
|
34ab6142db | ||
|
2232236a7a | ||
|
dcecd10c88 | ||
|
af07c7f050 | ||
|
95dba6dcaf | ||
|
90c2bf7c94 | ||
|
b5f04573f2 | ||
|
a54e58b4d6 | ||
|
219b00f660 | ||
|
fa777c5bc0 | ||
|
6d0683b055 | ||
|
25262cfb91 | ||
|
7a46b44d25 | ||
|
42f931f6cf | ||
|
2fe5c090aa | ||
|
ed218e73bb | ||
|
9a35386841 | ||
|
2b14bdae62 | ||
|
31b90d12a4 | ||
|
b4ffcc5555 | ||
|
57368c8c6c | ||
|
11ef22edec | ||
|
f78d01d770 | ||
|
7532acc95d | ||
|
ed84e56a85 | ||
|
b49e5d5c39 | ||
|
e7b2832967 | ||
|
5fda1f0f59 | ||
|
0d3414c6d6 | ||
|
fbf2df9e7a | ||
|
934d633d4d |
@@ -31,6 +31,9 @@ tsconfig.json
|
|||||||
/tmp
|
/tmp
|
||||||
/babel.config.js
|
/babel.config.js
|
||||||
/ecosystem.config.js
|
/ecosystem.config.js
|
||||||
|
/extra/healthcheck.exe
|
||||||
|
/extra/healthcheck
|
||||||
|
extra/exe-builder
|
||||||
|
|
||||||
### .gitignore content (commented rules are duplicated)
|
### .gitignore content (commented rules are duplicated)
|
||||||
|
|
||||||
@@ -45,6 +48,4 @@ dist-ssr
|
|||||||
#!/data/.gitkeep
|
#!/data/.gitkeep
|
||||||
#.vscode
|
#.vscode
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### End of .gitignore content
|
### End of .gitignore content
|
||||||
|
@@ -19,3 +19,6 @@ indent_size = 2
|
|||||||
|
|
||||||
[*.vue]
|
[*.vue]
|
||||||
trim_trailing_whitespace = false
|
trim_trailing_whitespace = false
|
||||||
|
|
||||||
|
[*.go]
|
||||||
|
indent_style = tab
|
||||||
|
6
.github/ISSUE_TEMPLATE/ask-for-help.yaml
vendored
6
.github/ISSUE_TEMPLATE/ask-for-help.yaml
vendored
@@ -26,6 +26,12 @@ body:
|
|||||||
label: "📝 Describe your problem"
|
label: "📝 Describe your problem"
|
||||||
description: "Please walk us through it step by step."
|
description: "Please walk us through it step by step."
|
||||||
placeholder: "Describe what are you asking for..."
|
placeholder: "Describe what are you asking for..."
|
||||||
|
- type: textarea
|
||||||
|
id: error-msg
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
attributes:
|
||||||
|
label: "📝 Error Message(s) or Log"
|
||||||
- type: input
|
- type: input
|
||||||
id: uptime-kuma-version
|
id: uptime-kuma-version
|
||||||
attributes:
|
attributes:
|
||||||
|
4
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
4
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
@@ -61,8 +61,8 @@ body:
|
|||||||
id: operating-system
|
id: operating-system
|
||||||
attributes:
|
attributes:
|
||||||
label: "💻 Operating System and Arch"
|
label: "💻 Operating System and Arch"
|
||||||
description: "Which OS is your server/device running on?"
|
description: "Which OS is your server/device running on? (For Replit, please do not report this bug)"
|
||||||
placeholder: "Ex. Ubuntu 20.04 x86"
|
placeholder: "Ex. Ubuntu 20.04 x64 "
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
- type: input
|
- type: input
|
||||||
|
19
.github/ISSUE_TEMPLATE/security.md
vendored
Normal file
19
.github/ISSUE_TEMPLATE/security.md
vendored
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
---
|
||||||
|
|
||||||
|
name: "Security Issue"
|
||||||
|
about: "Just for alerting @louislam, do not provide any details here"
|
||||||
|
title: "Security Issue"
|
||||||
|
ref: "main"
|
||||||
|
labels:
|
||||||
|
|
||||||
|
- security
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
DO NOT PROVIDE ANY DETAILS HERE. Please privately report to https://github.com/louislam/uptime-kuma/security/advisories/new.
|
||||||
|
|
||||||
|
|
||||||
|
Why need this issue? It is because GitHub Advisory do not send a notification to @louislam, it is a workaround to do so.
|
||||||
|
|
||||||
|
Your GitHub Advisory URL:
|
||||||
|
|
1
.github/PULL_REQUEST_TEMPLATE.md
vendored
1
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -16,7 +16,6 @@ Please delete any options that are not relevant.
|
|||||||
- User interface (UI)
|
- User interface (UI)
|
||||||
- New feature (non-breaking change which adds functionality)
|
- New feature (non-breaking change which adds functionality)
|
||||||
- Breaking change (fix or feature that would cause existing functionality to not work as expected)
|
- Breaking change (fix or feature that would cause existing functionality to not work as expected)
|
||||||
- Translation update
|
|
||||||
- Other
|
- Other
|
||||||
- This change requires a documentation update
|
- This change requires a documentation update
|
||||||
|
|
||||||
|
1
.github/config/exclude.txt
vendored
Normal file
1
.github/config/exclude.txt
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
# This is a .gitignore style file for 'GrantBirki/json-yaml-validate' Action workflow
|
23
.github/workflows/auto-test.yml
vendored
23
.github/workflows/auto-test.yml
vendored
@@ -6,8 +6,12 @@ name: Auto Test
|
|||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [ master ]
|
branches: [ master ]
|
||||||
|
paths-ignore:
|
||||||
|
- '*.md'
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [ master ]
|
branches: [ master ]
|
||||||
|
paths-ignore:
|
||||||
|
- '*.md'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
auto-test:
|
auto-test:
|
||||||
@@ -18,7 +22,7 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
os: [macos-latest, ubuntu-latest, windows-latest]
|
os: [macos-latest, ubuntu-latest, windows-latest]
|
||||||
node: [ 14, 16, 17, 18 ]
|
node: [ 14, 16, 18, 20 ]
|
||||||
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
|
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
@@ -36,6 +40,7 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
HEADLESS_TEST: 1
|
HEADLESS_TEST: 1
|
||||||
JUST_FOR_TEST: ${{ secrets.JUST_FOR_TEST }}
|
JUST_FOR_TEST: ${{ secrets.JUST_FOR_TEST }}
|
||||||
|
|
||||||
check-linters:
|
check-linters:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
@@ -66,3 +71,19 @@ jobs:
|
|||||||
- run: npm install
|
- run: npm install
|
||||||
- run: npm run build
|
- run: npm run build
|
||||||
- run: npm run cy:test
|
- run: npm run cy:test
|
||||||
|
|
||||||
|
frontend-unit-tests:
|
||||||
|
needs: [ check-linters ]
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- run: git config --global core.autocrlf false # Mainly for Windows
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Use Node.js 14
|
||||||
|
uses: actions/setup-node@v3
|
||||||
|
with:
|
||||||
|
node-version: 14
|
||||||
|
cache: 'npm'
|
||||||
|
- run: npm install
|
||||||
|
- run: npm run build
|
||||||
|
- run: npm run cy:run:unit
|
||||||
|
7
.github/workflows/close-incorrect-issue.yml
vendored
7
.github/workflows/close-incorrect-issue.yml
vendored
@@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
name: Close Incorrect Issue
|
name: Close Incorrect Issue
|
||||||
|
|
||||||
on:
|
on:
|
||||||
@@ -12,13 +11,13 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
os: [ubuntu-latest]
|
os: [ubuntu-latest]
|
||||||
node-version: [16.x]
|
node-version: [16]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Use Node.js ${{ matrix.node-version }}
|
- name: Use Node.js ${{ matrix.node-version }}
|
||||||
uses: actions/setup-node@v2
|
uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node-version }}
|
node-version: ${{ matrix.node-version }}
|
||||||
cache: 'npm'
|
cache: 'npm'
|
||||||
|
26
.github/workflows/json-yaml-validate.yml
vendored
Normal file
26
.github/workflows/json-yaml-validate.yml
vendored
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
name: json-yaml-validate
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
pull-requests: write # enable write permissions for pull request comments
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
json-yaml-validate:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: json-yaml-validate
|
||||||
|
id: json-yaml-validate
|
||||||
|
uses: GrantBirki/json-yaml-validate@v1.3.0
|
||||||
|
with:
|
||||||
|
comment: "true" # enable comment mode
|
||||||
|
exclude_file: ".github/config/exclude.txt" # gitignore style file for exclusions
|
10
.github/workflows/stale-bot.yml
vendored
10
.github/workflows/stale-bot.yml
vendored
@@ -3,22 +3,20 @@ on:
|
|||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
schedule:
|
schedule:
|
||||||
- cron: '0 */6 * * *'
|
- cron: '0 */6 * * *'
|
||||||
#Run every 6 hours
|
#Run every 6 hours
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
stale:
|
stale:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/stale@v5
|
- uses: actions/stale@v7
|
||||||
with:
|
with:
|
||||||
stale-issue-message: 'We are clearing up our old issues and your ticket has been open for 3 months with no activity. Remove stale label or comment or this will be closed in 2 days.'
|
stale-issue-message: 'We are clearing up our old issues and your ticket has been open for 3 months with no activity. Remove stale label or comment or this will be closed in 2 days.'
|
||||||
stale-pr-message: 'We are clearing up our old Pull Requests and yours has been open for 3 months with no activity. Remove stale label or comment or this will be closed in 2 days.'
|
|
||||||
close-issue-message: 'This issue was closed because it has been stalled for 2 days with no activity.'
|
close-issue-message: 'This issue was closed because it has been stalled for 2 days with no activity.'
|
||||||
close-pr-message: 'This PR was closed because it has been stalled for 2 days with no activity.'
|
|
||||||
days-before-stale: 90
|
days-before-stale: 90
|
||||||
days-before-close: 2
|
days-before-close: 2
|
||||||
|
days-before-pr-stale: 999999999
|
||||||
|
days-before-pr-close: 1
|
||||||
exempt-issue-labels: 'News,Medium,High,discussion,bug,doc,feature-request'
|
exempt-issue-labels: 'News,Medium,High,discussion,bug,doc,feature-request'
|
||||||
exempt-pr-labels: 'awaiting-approval,work-in-progress,enhancement,feature-request'
|
|
||||||
exempt-issue-assignees: 'louislam'
|
exempt-issue-assignees: 'louislam'
|
||||||
exempt-pr-assignees: 'louislam'
|
|
||||||
operations-per-run: 200
|
operations-per-run: 200
|
||||||
|
10
.gitignore
vendored
10
.gitignore
vendored
@@ -16,3 +16,13 @@ dist-ssr
|
|||||||
|
|
||||||
cypress/videos
|
cypress/videos
|
||||||
cypress/screenshots
|
cypress/screenshots
|
||||||
|
|
||||||
|
/extra/healthcheck.exe
|
||||||
|
/extra/healthcheck
|
||||||
|
/extra/healthcheck-armv7
|
||||||
|
|
||||||
|
extra/exe-builder/bin
|
||||||
|
extra/exe-builder/obj
|
||||||
|
|
||||||
|
.vs
|
||||||
|
.vscode
|
||||||
|
@@ -10,5 +10,6 @@
|
|||||||
"color-function-notation": "legacy",
|
"color-function-notation": "legacy",
|
||||||
"shorthand-property-no-redundant-values": null,
|
"shorthand-property-no-redundant-values": null,
|
||||||
"color-hex-length": null,
|
"color-hex-length": null,
|
||||||
|
"declaration-block-no-redundant-longhand-properties": null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
# Project Info
|
# Project Info
|
||||||
|
|
||||||
First of all, thank you everyone who made pull requests for Uptime Kuma, I never thought GitHub Community can be that nice! And also because of this, I also never thought other people actually read my code and edit my code. It is not structured and commented so well, lol. Sorry about that.
|
First of all, I want to thank everyone who made pull requests for Uptime Kuma. I never thought the GitHub Community would be so nice! Because of this, I also never thought that other people would actually read and edit my code. It is not very well structured or commented, sorry about that.
|
||||||
|
|
||||||
The project was created with vite.js (vue3). Then I created a subdirectory called "server" for server part. Both frontend and backend share the same package.json.
|
The project was created with vite.js (vue3). Then I created a subdirectory called "server" for server part. Both frontend and backend share the same package.json.
|
||||||
|
|
||||||
@@ -17,8 +17,11 @@ The frontend code build into "dist" directory. The server (express.js) exposes t
|
|||||||
|
|
||||||
## Directories
|
## Directories
|
||||||
|
|
||||||
|
- config (dev config files)
|
||||||
- data (App data)
|
- data (App data)
|
||||||
|
- db (Base database and migration scripts)
|
||||||
- dist (Frontend build)
|
- dist (Frontend build)
|
||||||
|
- docker (Dockerfiles)
|
||||||
- extra (Extra useful scripts)
|
- extra (Extra useful scripts)
|
||||||
- public (Frontend resources for dev only)
|
- public (Frontend resources for dev only)
|
||||||
- server (Server source code)
|
- server (Server source code)
|
||||||
@@ -27,20 +30,23 @@ The frontend code build into "dist" directory. The server (express.js) exposes t
|
|||||||
|
|
||||||
## Can I create a pull request for Uptime Kuma?
|
## Can I create a pull request for Uptime Kuma?
|
||||||
|
|
||||||
Yes or no, it depends on what you will try to do. Since I don't want to waste your time, be sure to **create an empty draft pull request or open an issue, so we can discuss first**. Especially for a large pull request or you don't know it will be merged or not.
|
Yes or no, it depends on what you will try to do. Since I don't want to waste your time, be sure to **create an empty draft pull request or open an issue, so we can have a discussion first**. Especially for a large pull request or you don't know it will be merged or not.
|
||||||
|
|
||||||
Here are some references:
|
Here are some references:
|
||||||
|
|
||||||
✅ Usually Accept:
|
✅ Usually Accept:
|
||||||
- Bug/Security fix
|
- Bug fix
|
||||||
- Translations
|
- Security fix
|
||||||
- Adding notification providers
|
- Adding notification providers
|
||||||
|
- Adding new language files (You should go to https://weblate.kuma.pet for existing languages)
|
||||||
|
- Adding new language keys: `$t("...")`
|
||||||
|
|
||||||
⚠️ Discussion First
|
⚠️ Discussion First
|
||||||
- Large pull requests
|
- Large pull requests
|
||||||
- New features
|
- New features
|
||||||
|
|
||||||
❌ Won't Merge
|
❌ Won't Merge
|
||||||
|
- A dedicated pr for translating existing languages (You can now translate on https://weblate.kuma.pet)
|
||||||
- Do not pass auto test
|
- Do not pass auto test
|
||||||
- Any breaking changes
|
- Any breaking changes
|
||||||
- Duplicated pull request
|
- Duplicated pull request
|
||||||
@@ -48,8 +54,13 @@ Here are some references:
|
|||||||
- UI/UX is not close to Uptime Kuma
|
- UI/UX is not close to Uptime Kuma
|
||||||
- Existing logic is completely modified or deleted for no reason
|
- Existing logic is completely modified or deleted for no reason
|
||||||
- A function that is completely out of scope
|
- A function that is completely out of scope
|
||||||
|
- Convert existing code into other programming languages
|
||||||
- Unnecessary large code changes (Hard to review, causes code conflicts to other pull requests)
|
- Unnecessary large code changes (Hard to review, causes code conflicts to other pull requests)
|
||||||
|
|
||||||
|
The above cases cannot cover all situations.
|
||||||
|
|
||||||
|
I (@louislam) have the final say. If your pull request does not meet my expectations, I will reject it, no matter how much time you spend on it. Therefore, it is essential to have a discussion beforehand.
|
||||||
|
|
||||||
I will mark your pull request in the [milestones](https://github.com/louislam/uptime-kuma/milestones), if I am plan to review and merge it.
|
I will mark your pull request in the [milestones](https://github.com/louislam/uptime-kuma/milestones), if I am plan to review and merge it.
|
||||||
|
|
||||||
Also, please don't rush or ask for ETA, because I have to understand the pull request, make sure it is no breaking changes and stick to my vision of this project, especially for large pull requests.
|
Also, please don't rush or ask for ETA, because I have to understand the pull request, make sure it is no breaking changes and stick to my vision of this project, especially for large pull requests.
|
||||||
@@ -72,13 +83,13 @@ Before deep into coding, discussion first is preferred. Creating an empty pull r
|
|||||||
|
|
||||||
## 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.
|
I personally do not like something that requires so many configurations before you can finally start the app. I hope Uptime Kuma installation could be as easy as like installing a mobile app.
|
||||||
|
|
||||||
- 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
|
- Easy to install for non-Docker users, no native build dependency is needed (for x86_64/armv7/arm64), no extra config, no extra effort required to get it running
|
||||||
- Single container for Docker users, no very complex docker-compose file. Just map the volume and expose the port, then good to go
|
- 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. Environment variable is not encouraged, unless it is related to startup such as `DATA_DIR`.
|
- Settings should be configurable in the frontend. Environment variable is not encouraged, unless it is related to startup such as `DATA_DIR`
|
||||||
- Easy to use
|
- Easy to use
|
||||||
- The web UI styling should be consistent and nice.
|
- The web UI styling should be consistent and nice
|
||||||
|
|
||||||
## Coding Styles
|
## Coding Styles
|
||||||
|
|
||||||
@@ -87,7 +98,7 @@ I personally do not like something need to learn so much and need to config so m
|
|||||||
- Follow ESLint
|
- Follow ESLint
|
||||||
- Methods and functions should be documented with JSDoc
|
- Methods and functions should be documented with JSDoc
|
||||||
|
|
||||||
## Name convention
|
## Name Conventions
|
||||||
|
|
||||||
- Javascript/Typescript: camelCaseType
|
- Javascript/Typescript: camelCaseType
|
||||||
- SQLite: snake_case (Underscore)
|
- SQLite: snake_case (Underscore)
|
||||||
@@ -101,7 +112,7 @@ I personally do not like something need to learn so much and need to config so m
|
|||||||
- IDE that supports ESLint and EditorConfig (I am using IntelliJ IDEA)
|
- IDE that supports ESLint and EditorConfig (I am using IntelliJ IDEA)
|
||||||
- A SQLite GUI tool (SQLite Expert Personal is suggested)
|
- A SQLite GUI tool (SQLite Expert Personal is suggested)
|
||||||
|
|
||||||
## Install dependencies
|
## Install Dependencies for Development
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm ci
|
npm ci
|
||||||
@@ -119,6 +130,12 @@ Port `3000` and port `3001` will be used.
|
|||||||
npm run dev
|
npm run dev
|
||||||
```
|
```
|
||||||
|
|
||||||
|
But sometimes, you would like to keep restart the server, but not the frontend, you can run these command in two terminals:
|
||||||
|
```
|
||||||
|
npm run start-frontend-dev
|
||||||
|
npm run start-server-dev
|
||||||
|
```
|
||||||
|
|
||||||
## Backend Server
|
## Backend Server
|
||||||
|
|
||||||
It binds to `0.0.0.0:3001` by default.
|
It binds to `0.0.0.0:3001` by default.
|
||||||
@@ -134,12 +151,15 @@ express.js is used for:
|
|||||||
|
|
||||||
### Structure in /server/
|
### Structure in /server/
|
||||||
|
|
||||||
|
- jobs/ (Jobs that are running in another process)
|
||||||
- model/ (Object model, auto mapping to the database table name)
|
- model/ (Object model, auto mapping to the database table name)
|
||||||
- modules/ (Modified 3rd-party modules)
|
- modules/ (Modified 3rd-party modules)
|
||||||
|
- monitor_types (Monitor Types)
|
||||||
- notification-providers/ (individual notification logic)
|
- notification-providers/ (individual notification logic)
|
||||||
- routers/ (Express Routers)
|
- routers/ (Express Routers)
|
||||||
- socket-handler (Socket.io Handlers)
|
- socket-handler (Socket.io Handlers)
|
||||||
- server.js (Server entry point and main logic)
|
- server.js (Server entry point)
|
||||||
|
- uptime-kuma-server.js (UptimeKumaServer class, main logic should be here, but some still in `server.js`)
|
||||||
|
|
||||||
## Frontend Dev Server
|
## Frontend Dev Server
|
||||||
|
|
||||||
@@ -172,15 +192,11 @@ The data and socket logic are in `src/mixins/socket.js`.
|
|||||||
|
|
||||||
## Unit Test
|
## Unit Test
|
||||||
|
|
||||||
It is an end-to-end testing. It is using Jest and Puppeteer.
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm run build
|
npm run build
|
||||||
npm test
|
npm test
|
||||||
```
|
```
|
||||||
|
|
||||||
By default, the Chromium window will be shown up during the test. Specifying `HEADLESS_TEST=1` for terminal environments.
|
|
||||||
|
|
||||||
## Dependencies
|
## Dependencies
|
||||||
|
|
||||||
Both frontend and backend share the same package.json. However, the frontend dependencies are eventually not used in the production environment, because it is usually also baked into dist files. So:
|
Both frontend and backend share the same package.json. However, the frontend dependencies are eventually not used in the production environment, because it is usually also baked into dist files. So:
|
||||||
@@ -194,18 +210,12 @@ Both frontend and backend share the same package.json. However, the frontend dep
|
|||||||
|
|
||||||
### Update Dependencies
|
### Update Dependencies
|
||||||
|
|
||||||
Install `ncu`
|
|
||||||
https://github.com/raineorshine/npm-check-updates
|
|
||||||
|
|
||||||
```bash
|
|
||||||
ncu -u -t patch
|
|
||||||
npm install
|
|
||||||
```
|
|
||||||
|
|
||||||
Since previously updating Vite 2.5.10 to 2.6.0 broke the application completely, from now on, it should update patch release version only.
|
Since previously updating Vite 2.5.10 to 2.6.0 broke the application completely, from now on, it should update patch release version only.
|
||||||
|
|
||||||
Patch release = the third digit ([Semantic Versioning](https://semver.org/))
|
Patch release = the third digit ([Semantic Versioning](https://semver.org/))
|
||||||
|
|
||||||
|
If for maybe security reasons, a library must be updated. Then you must need to check if there are any breaking changes.
|
||||||
|
|
||||||
## Translations
|
## Translations
|
||||||
|
|
||||||
Please read: https://github.com/louislam/uptime-kuma/tree/master/src/languages
|
Please read: https://github.com/louislam/uptime-kuma/tree/master/src/languages
|
||||||
@@ -225,12 +235,13 @@ https://github.com/louislam/uptime-kuma/issues?q=sort%3Aupdated-desc
|
|||||||
|
|
||||||
1. Draft a release note
|
1. Draft a release note
|
||||||
2. Make sure the repo is cleared
|
2. Make sure the repo is cleared
|
||||||
|
3. If the healthcheck is updated, remember to re-compile it: `npm run build-docker-builder-go`
|
||||||
3. `npm run release-final with env vars: `VERSION` and `GITHUB_TOKEN`
|
3. `npm run release-final with env vars: `VERSION` and `GITHUB_TOKEN`
|
||||||
4. Wait until the `Press any key to continue`
|
4. Wait until the `Press any key to continue`
|
||||||
5. `git push`
|
5. `git push`
|
||||||
6. Publish the release note as 1.X.X
|
6. Publish the release note as 1.X.X
|
||||||
7. Press any key to continue
|
7. Press any key to continue
|
||||||
8. SSH to demo site server and update to 1.X.X
|
8. Deploy to the demo server: `npm run deploy-demo-server`
|
||||||
|
|
||||||
Checking:
|
Checking:
|
||||||
|
|
||||||
|
63
README.md
63
README.md
@@ -1,38 +1,39 @@
|
|||||||
# Uptime Kuma
|
# Uptime Kuma
|
||||||
|
|
||||||
<a target="_blank" href="https://github.com/louislam/uptime-kuma"><img src="https://img.shields.io/github/stars/louislam/uptime-kuma" /></a> <a target="_blank" href="https://hub.docker.com/r/louislam/uptime-kuma"><img src="https://img.shields.io/docker/pulls/louislam/uptime-kuma" /></a> <a target="_blank" href="https://hub.docker.com/r/louislam/uptime-kuma"><img src="https://img.shields.io/docker/v/louislam/uptime-kuma/latest?label=docker%20image%20ver." /></a> <a target="_blank" href="https://github.com/louislam/uptime-kuma"><img src="https://img.shields.io/github/last-commit/louislam/uptime-kuma" /></a> <a target="_blank" href="https://opencollective.com/uptime-kuma"><img src="https://opencollective.com/uptime-kuma/total/badge.svg?label=Open%20Collective%20Backers&color=brightgreen" /></a>
|
<a target="_blank" href="https://github.com/louislam/uptime-kuma"><img src="https://img.shields.io/github/stars/louislam/uptime-kuma" /></a> <a target="_blank" href="https://hub.docker.com/r/louislam/uptime-kuma"><img src="https://img.shields.io/docker/pulls/louislam/uptime-kuma" /></a> <a target="_blank" href="https://hub.docker.com/r/louislam/uptime-kuma"><img src="https://img.shields.io/docker/v/louislam/uptime-kuma/latest?label=docker%20image%20ver." /></a> <a target="_blank" href="https://github.com/louislam/uptime-kuma"><img src="https://img.shields.io/github/last-commit/louislam/uptime-kuma" /></a> <a target="_blank" href="https://opencollective.com/uptime-kuma"><img src="https://opencollective.com/uptime-kuma/total/badge.svg?label=Open%20Collective%20Backers&color=brightgreen" /></a>
|
||||||
[](https://github.com/sponsors/louislam)
|
[](https://github.com/sponsors/louislam) <a href="https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/">
|
||||||
|
<img src="https://weblate.kuma.pet/widgets/uptime-kuma/-/svg-badge.svg" alt="Translation status" />
|
||||||
|
</a>
|
||||||
|
|
||||||
<div align="center" width="100%">
|
<div align="center" width="100%">
|
||||||
<img src="./public/icon.svg" width="128" alt="" />
|
<img src="./public/icon.svg" width="128" alt="" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
It is a self-hosted monitoring tool like "Uptime Robot".
|
Uptime Kuma is an easy-to-use self-hosted monitoring tool.
|
||||||
|
|
||||||
<img src="https://uptime.kuma.pet/img/dark.jpg" width="700" alt="" />
|
<img src="https://user-images.githubusercontent.com/1336778/212262296-e6205815-ad62-488c-83ec-a5b0d0689f7c.jpg" width="700" alt="" />
|
||||||
|
|
||||||
## 🥔 Live Demo
|
## 🥔 Live Demo
|
||||||
|
|
||||||
Try it!
|
Try it!
|
||||||
|
|
||||||
- Tokyo Demo Server: https://demo.uptime.kuma.pet (Sponsored by [Uptime Kuma Sponsors](https://github.com/louislam/uptime-kuma#%EF%B8%8F-sponsors))
|
- Tokyo Demo Server: https://demo.uptime.kuma.pet (Sponsored by [Uptime Kuma Sponsors](https://github.com/louislam/uptime-kuma#%EF%B8%8F-sponsors))
|
||||||
- Europe Demo Server: https://demo.uptime-kuma.karimi.dev:27000 (Provided by [@mhkarimi1383](https://github.com/mhkarimi1383))
|
|
||||||
|
|
||||||
It is a temporary live demo, all data will be deleted after 10 minutes. Use the one that is closer to you, but I suggest that you should install and try it out for the best demo experience.
|
It is a temporary live demo, all data will be deleted after 10 minutes. Use the one that is closer to you, but I suggest that you should install and try it out for the best demo experience.
|
||||||
|
|
||||||
## ⭐ Features
|
## ⭐ Features
|
||||||
|
|
||||||
* Monitoring uptime for HTTP(s) / TCP / HTTP(s) Keyword / Ping / DNS Record / Push / Steam Game Server / Docker Containers.
|
* Monitoring uptime for HTTP(s) / TCP / HTTP(s) Keyword / Ping / DNS Record / Push / Steam Game Server / Docker Containers
|
||||||
* Fancy, Reactive, Fast UI/UX.
|
* Fancy, Reactive, Fast UI/UX
|
||||||
* Notifications via Telegram, Discord, Gotify, Slack, Pushover, Email (SMTP), and [90+ notification services, click here for the full list](https://github.com/louislam/uptime-kuma/tree/master/src/components/notifications).
|
* Notifications via Telegram, Discord, Gotify, Slack, Pushover, Email (SMTP), and [90+ notification services, click here for the full list](https://github.com/louislam/uptime-kuma/tree/master/src/components/notifications)
|
||||||
* 20 second intervals.
|
* 20 second intervals
|
||||||
* [Multi Languages](https://github.com/louislam/uptime-kuma/tree/master/src/languages)
|
* [Multi Languages](https://github.com/louislam/uptime-kuma/tree/master/src/lang)
|
||||||
* Multiple Status Pages
|
* Multiple status pages
|
||||||
* Map Status Page to Domain
|
* Map status pages to specific domains
|
||||||
* Ping Chart
|
* Ping chart
|
||||||
* Certificate Info
|
* Certificate info
|
||||||
* Proxy Support
|
* Proxy support
|
||||||
* 2FA available
|
* 2FA support
|
||||||
|
|
||||||
## 🔧 How to Install
|
## 🔧 How to Install
|
||||||
|
|
||||||
@@ -44,14 +45,19 @@ docker run -d --restart=always -p 3001:3001 -v uptime-kuma:/app/data --name upti
|
|||||||
|
|
||||||
⚠️ Please use a **local volume** only. Other types such as NFS are not supported.
|
⚠️ Please use a **local volume** only. Other types such as NFS are not supported.
|
||||||
|
|
||||||
Browse to http://localhost:3001 after starting.
|
Uptime Kuma is now running on http://localhost:3001
|
||||||
|
|
||||||
### 💪🏻 Non-Docker
|
### 💪🏻 Non-Docker
|
||||||
|
|
||||||
Required Tools:
|
Requirements:
|
||||||
- [Node.js](https://nodejs.org/en/download/) >= 14
|
- Platform
|
||||||
|
- ✅ Major Linux distros such as Debian, Ubuntu, CentOS, Fedora and ArchLinux etc.
|
||||||
|
- ✅ Windows 10 (x64), Windows Server 2012 R2 (x64) or higher
|
||||||
|
- ❌ Replit / Heroku
|
||||||
|
- [Node.js](https://nodejs.org/en/download/) 14 / 16 / 18 (20 is not supported)
|
||||||
|
- [npm](https://docs.npmjs.com/cli/) >= 7
|
||||||
- [Git](https://git-scm.com/downloads)
|
- [Git](https://git-scm.com/downloads)
|
||||||
- [pm2](https://pm2.keymetrics.io/) - For run in background
|
- [pm2](https://pm2.keymetrics.io/) - For running Uptime Kuma in the background
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Update your npm to the latest version
|
# Update your npm to the latest version
|
||||||
@@ -73,7 +79,7 @@ pm2 start server/server.js --name uptime-kuma
|
|||||||
|
|
||||||
|
|
||||||
```
|
```
|
||||||
Browse to http://localhost:3001 after starting.
|
Uptime Kuma is now running on http://localhost:3001
|
||||||
|
|
||||||
More useful PM2 Commands
|
More useful PM2 Commands
|
||||||
|
|
||||||
@@ -85,6 +91,10 @@ pm2 monit
|
|||||||
pm2 save && pm2 startup
|
pm2 save && pm2 startup
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Windows Portable (x64)
|
||||||
|
|
||||||
|
https://github.com/louislam/uptime-kuma/releases/download/1.21.0/uptime-kuma-win64-portable-1.0.0.zip
|
||||||
|
|
||||||
### Advanced Installation
|
### Advanced Installation
|
||||||
|
|
||||||
If you need more options or need to browse via a reverse proxy, please read:
|
If you need more options or need to browse via a reverse proxy, please read:
|
||||||
@@ -142,17 +152,18 @@ Telegram Notification Sample:
|
|||||||
|
|
||||||
If you love this project, please consider giving me a ⭐.
|
If you love this project, please consider giving me a ⭐.
|
||||||
|
|
||||||
## 🗣️ Discussion
|
## 🗣️ Discussion / Ask for Help
|
||||||
|
|
||||||
### Issues Page
|
⚠️ For any general or technical questions, please don't send me an email, as I am unable to provide support in that manner. I will not response if you asked such questions.
|
||||||
|
|
||||||
You can discuss or ask for help in [issues](https://github.com/louislam/uptime-kuma/issues).
|
I recommend using Google, GitHub Issues, or Uptime Kuma's Subreddit for finding answers to your question. If you cannot find the information you need, feel free to ask:
|
||||||
|
|
||||||
### Subreddit
|
- [GitHub Issues](https://github.com/louislam/uptime-kuma/issues)
|
||||||
|
- [Subreddit r/Uptime kuma](https://www.reddit.com/r/UptimeKuma/)
|
||||||
|
|
||||||
My Reddit account: [u/louislamlam](https://reddit.com/u/louislamlam).
|
My Reddit account: [u/louislamlam](https://reddit.com/u/louislamlam).
|
||||||
You can mention me if you ask a question on Reddit.
|
You can mention me if you ask a question on Reddit.
|
||||||
[r/Uptime kuma](https://www.reddit.com/r/UptimeKuma/)
|
|
||||||
|
|
||||||
## Contribute
|
## Contribute
|
||||||
|
|
||||||
@@ -171,7 +182,7 @@ Check out the latest beta release here: https://github.com/louislam/uptime-kuma/
|
|||||||
If you want to report a bug or request a new feature, feel free to open a [new issue](https://github.com/louislam/uptime-kuma/issues).
|
If you want to report a bug or request a new feature, feel free to open a [new issue](https://github.com/louislam/uptime-kuma/issues).
|
||||||
|
|
||||||
### Translations
|
### Translations
|
||||||
If you want to translate Uptime Kuma into your language, please read: https://github.com/louislam/uptime-kuma/tree/master/src/languages
|
If you want to translate Uptime Kuma into your language, please visit [Weblate Readme](https://github.com/louislam/uptime-kuma/blob/master/src/lang/README.md).
|
||||||
|
|
||||||
Feel free to correct my grammar in this README, source code, or wiki, as my mother language is not English and my grammar is not that great.
|
Feel free to correct my grammar in this README, source code, or wiki, as my mother language is not English and my grammar is not that great.
|
||||||
|
|
||||||
|
@@ -2,9 +2,14 @@
|
|||||||
|
|
||||||
## Reporting a Vulnerability
|
## Reporting a Vulnerability
|
||||||
|
|
||||||
Please report security issues to uptime@kuma.pet.
|
1. Please report security issues to https://github.com/louislam/uptime-kuma/security/advisories/new.
|
||||||
|
1. Please also create a empty security issues for alerting me, as GitHub Advisory do not send a notification, I probably will miss without this. https://github.com/louislam/uptime-kuma/issues/new?assignees=&labels=help&template=security.md
|
||||||
|
|
||||||
Do not use the issue tracker or discuss it in the public as it will cause more damage.
|
Do not use the public issue tracker or discuss it in the public as it will cause more damage.
|
||||||
|
|
||||||
|
## Do you accept other 3rd-party bug bounty platforms?
|
||||||
|
|
||||||
|
At this moment, I DO NOT accept other bug bounty platforms, because I am not familiar with these platforms and someone have tried to send a phishing link to me by this already. To minimize my own risk, please report through GitHub Advisories only. I will ignore all 3rd-party bug bounty platforms emails.
|
||||||
|
|
||||||
## Supported Versions
|
## Supported Versions
|
||||||
|
|
||||||
|
10
config/cypress.frontend.config.js
Normal file
10
config/cypress.frontend.config.js
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
const { defineConfig } = require("cypress");
|
||||||
|
|
||||||
|
module.exports = defineConfig({
|
||||||
|
e2e: {
|
||||||
|
supportFile: false,
|
||||||
|
specPattern: [
|
||||||
|
"test/cypress/unit/**/*.js"
|
||||||
|
],
|
||||||
|
}
|
||||||
|
});
|
7
db/patch-add-description-monitor.sql
Normal file
7
db/patch-add-description-monitor.sql
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
-- You should not modify if this have pushed to Github, unless it does serious wrong with the db.
|
||||||
|
BEGIN TRANSACTION;
|
||||||
|
|
||||||
|
ALTER TABLE monitor
|
||||||
|
ADD description TEXT default null;
|
||||||
|
|
||||||
|
COMMIT;
|
5
db/patch-add-gamedig-monitor.sql
Normal file
5
db/patch-add-gamedig-monitor.sql
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
BEGIN TRANSACTION;
|
||||||
|
|
||||||
|
ALTER TABLE monitor
|
||||||
|
ADD game VARCHAR(255);
|
||||||
|
COMMIT
|
4
db/patch-add-google-analytics-status-page-tag.sql
Normal file
4
db/patch-add-google-analytics-status-page-tag.sql
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
-- You should not modify if this have pushed to Github, unless it does serious wrong with the db.
|
||||||
|
BEGIN TRANSACTION;
|
||||||
|
ALTER TABLE status_page ADD google_analytics_tag_id VARCHAR;
|
||||||
|
COMMIT;
|
6
db/patch-add-parent-monitor.sql
Normal file
6
db/patch-add-parent-monitor.sql
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
BEGIN TRANSACTION;
|
||||||
|
|
||||||
|
ALTER TABLE monitor
|
||||||
|
ADD parent INTEGER REFERENCES [monitor] ([id]) ON DELETE SET NULL ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
COMMIT
|
13
db/patch-api-key-table.sql
Normal file
13
db/patch-api-key-table.sql
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
-- You should not modify if this have pushed to Github, unless it does serious wrong with the db.
|
||||||
|
BEGIN TRANSACTION;
|
||||||
|
CREATE TABLE [api_key] (
|
||||||
|
[id] INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||||
|
[key] VARCHAR(255) NOT NULL,
|
||||||
|
[name] VARCHAR(255) NOT NULL,
|
||||||
|
[user_id] INTEGER NOT NULL,
|
||||||
|
[created_date] DATETIME DEFAULT (DATETIME('now')) NOT NULL,
|
||||||
|
[active] BOOLEAN DEFAULT 1 NOT NULL,
|
||||||
|
[expires] DATETIME DEFAULT NULL,
|
||||||
|
CONSTRAINT FK_user FOREIGN KEY ([user_id]) REFERENCES [user]([id]) ON DELETE CASCADE ON UPDATE CASCADE
|
||||||
|
);
|
||||||
|
COMMIT;
|
25
db/patch-grpc-monitor.sql
Normal file
25
db/patch-grpc-monitor.sql
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
-- You should not modify if this have pushed to Github, unless it does serious wrong with the db.
|
||||||
|
BEGIN TRANSACTION;
|
||||||
|
|
||||||
|
ALTER TABLE monitor
|
||||||
|
ADD grpc_url VARCHAR(255) default null;
|
||||||
|
|
||||||
|
ALTER TABLE monitor
|
||||||
|
ADD grpc_protobuf TEXT default null;
|
||||||
|
|
||||||
|
ALTER TABLE monitor
|
||||||
|
ADD grpc_body TEXT default null;
|
||||||
|
|
||||||
|
ALTER TABLE monitor
|
||||||
|
ADD grpc_metadata TEXT default null;
|
||||||
|
|
||||||
|
ALTER TABLE monitor
|
||||||
|
ADD grpc_method VARCHAR(255) default null;
|
||||||
|
|
||||||
|
ALTER TABLE monitor
|
||||||
|
ADD grpc_service_name VARCHAR(255) default null;
|
||||||
|
|
||||||
|
ALTER TABLE monitor
|
||||||
|
ADD grpc_enable_tls BOOLEAN default 0 not null;
|
||||||
|
|
||||||
|
COMMIT;
|
12
db/patch-http-body-encoding.sql
Normal file
12
db/patch-http-body-encoding.sql
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
-- You should not modify if this have pushed to Github, unless it does serious wrong with the db.
|
||||||
|
BEGIN TRANSACTION;
|
||||||
|
|
||||||
|
ALTER TABLE monitor ADD http_body_encoding VARCHAR(25);
|
||||||
|
|
||||||
|
COMMIT;
|
||||||
|
|
||||||
|
BEGIN TRANSACTION;
|
||||||
|
|
||||||
|
UPDATE monitor SET http_body_encoding = 'json' WHERE (type = 'http' or type = 'keyword') AND http_body_encoding IS NULL;
|
||||||
|
|
||||||
|
COMMIT;
|
11
db/patch-maintenance-cron.sql
Normal file
11
db/patch-maintenance-cron.sql
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
-- You should not modify if this have pushed to Github, unless it does serious wrong with the db.
|
||||||
|
BEGIN TRANSACTION;
|
||||||
|
|
||||||
|
DROP TABLE maintenance_timeslot;
|
||||||
|
|
||||||
|
-- 999 characters. https://stackoverflow.com/questions/46134830/maximum-length-for-cron-job
|
||||||
|
ALTER TABLE maintenance ADD cron TEXT;
|
||||||
|
ALTER TABLE maintenance ADD timezone VARCHAR(255);
|
||||||
|
ALTER TABLE maintenance ADD duration INTEGER;
|
||||||
|
|
||||||
|
COMMIT;
|
83
db/patch-maintenance-table2.sql
Normal file
83
db/patch-maintenance-table2.sql
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
-- You should not modify if this have pushed to Github, unless it does serious wrong with the db.
|
||||||
|
BEGIN TRANSACTION;
|
||||||
|
|
||||||
|
-- Just for someone who tested maintenance before (patch-maintenance-table.sql)
|
||||||
|
DROP TABLE IF EXISTS maintenance_status_page;
|
||||||
|
DROP TABLE IF EXISTS monitor_maintenance;
|
||||||
|
DROP TABLE IF EXISTS maintenance;
|
||||||
|
DROP TABLE IF EXISTS maintenance_timeslot;
|
||||||
|
|
||||||
|
-- maintenance
|
||||||
|
CREATE TABLE [maintenance] (
|
||||||
|
[id] INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||||
|
[title] VARCHAR(150) NOT NULL,
|
||||||
|
[description] TEXT NOT NULL,
|
||||||
|
[user_id] INTEGER REFERENCES [user]([id]) ON DELETE SET NULL ON UPDATE CASCADE,
|
||||||
|
[active] BOOLEAN NOT NULL DEFAULT 1,
|
||||||
|
[strategy] VARCHAR(50) NOT NULL DEFAULT 'single',
|
||||||
|
[start_date] DATETIME,
|
||||||
|
[end_date] DATETIME,
|
||||||
|
[start_time] TIME,
|
||||||
|
[end_time] TIME,
|
||||||
|
[weekdays] VARCHAR2(250) DEFAULT '[]',
|
||||||
|
[days_of_month] TEXT DEFAULT '[]',
|
||||||
|
[interval_day] INTEGER
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX [manual_active] ON [maintenance] (
|
||||||
|
[strategy],
|
||||||
|
[active]
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX [active] ON [maintenance] ([active]);
|
||||||
|
|
||||||
|
CREATE INDEX [maintenance_user_id] ON [maintenance] ([user_id]);
|
||||||
|
|
||||||
|
-- maintenance_status_page
|
||||||
|
CREATE TABLE maintenance_status_page (
|
||||||
|
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||||
|
status_page_id INTEGER NOT NULL,
|
||||||
|
maintenance_id INTEGER NOT NULL,
|
||||||
|
CONSTRAINT FK_maintenance FOREIGN KEY (maintenance_id) REFERENCES maintenance (id) ON DELETE CASCADE ON UPDATE CASCADE,
|
||||||
|
CONSTRAINT FK_status_page FOREIGN KEY (status_page_id) REFERENCES status_page (id) ON DELETE CASCADE ON UPDATE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX [status_page_id_index]
|
||||||
|
ON [maintenance_status_page]([status_page_id]);
|
||||||
|
|
||||||
|
CREATE INDEX [maintenance_id_index]
|
||||||
|
ON [maintenance_status_page]([maintenance_id]);
|
||||||
|
|
||||||
|
-- maintenance_timeslot
|
||||||
|
CREATE TABLE [maintenance_timeslot] (
|
||||||
|
[id] INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||||
|
[maintenance_id] INTEGER NOT NULL CONSTRAINT [FK_maintenance] REFERENCES [maintenance]([id]) ON DELETE CASCADE ON UPDATE CASCADE,
|
||||||
|
[start_date] DATETIME NOT NULL,
|
||||||
|
[end_date] DATETIME,
|
||||||
|
[generated_next] BOOLEAN DEFAULT 0
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX [maintenance_id] ON [maintenance_timeslot] ([maintenance_id] DESC);
|
||||||
|
|
||||||
|
CREATE INDEX [active_timeslot_index] ON [maintenance_timeslot] (
|
||||||
|
[maintenance_id] DESC,
|
||||||
|
[start_date] DESC,
|
||||||
|
[end_date] DESC
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX [generated_next_index] ON [maintenance_timeslot] ([generated_next]);
|
||||||
|
|
||||||
|
-- monitor_maintenance
|
||||||
|
CREATE TABLE monitor_maintenance (
|
||||||
|
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||||
|
monitor_id INTEGER NOT NULL,
|
||||||
|
maintenance_id INTEGER NOT NULL,
|
||||||
|
CONSTRAINT FK_maintenance FOREIGN KEY (maintenance_id) REFERENCES maintenance (id) ON DELETE CASCADE ON UPDATE CASCADE,
|
||||||
|
CONSTRAINT FK_monitor FOREIGN KEY (monitor_id) REFERENCES monitor (id) ON DELETE CASCADE ON UPDATE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX [maintenance_id_index2] ON [monitor_maintenance]([maintenance_id]);
|
||||||
|
|
||||||
|
CREATE INDEX [monitor_id_index] ON [monitor_maintenance]([monitor_id]);
|
||||||
|
|
||||||
|
COMMIT;
|
13
db/patch-monitor-tls.sql
Normal file
13
db/patch-monitor-tls.sql
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
-- You should not modify if this have pushed to Github, unless it does serious wrong with the db.
|
||||||
|
BEGIN TRANSACTION;
|
||||||
|
|
||||||
|
ALTER TABLE monitor
|
||||||
|
ADD tls_ca TEXT default null;
|
||||||
|
|
||||||
|
ALTER TABLE monitor
|
||||||
|
ADD tls_cert TEXT default null;
|
||||||
|
|
||||||
|
ALTER TABLE monitor
|
||||||
|
ADD tls_key TEXT default null;
|
||||||
|
|
||||||
|
COMMIT;
|
5
db/patch-ping-packet-size.sql
Normal file
5
db/patch-ping-packet-size.sql
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
-- You should not modify if this have pushed to Github, unless it does serious wrong with the db.
|
||||||
|
BEGIN TRANSACTION;
|
||||||
|
ALTER TABLE monitor
|
||||||
|
ADD packet_size INTEGER DEFAULT 56 NOT NULL;
|
||||||
|
COMMIT;
|
@@ -3,6 +3,6 @@ FROM node:16-alpine3.12
|
|||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
# Install apprise, iputils for non-root ping, setpriv
|
# 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 && \
|
RUN apk add --no-cache iputils setpriv dumb-init python3 py3-cryptography py3-pip py3-six py3-yaml py3-click py3-markdown py3-requests py3-requests-oauthlib git && \
|
||||||
pip3 --no-cache-dir install apprise==1.0.0 && \
|
pip3 --no-cache-dir install apprise==1.4.0 && \
|
||||||
rm -rf /root/.cache
|
rm -rf /root/.cache
|
||||||
|
16
docker/builder-go.dockerfile
Normal file
16
docker/builder-go.dockerfile
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
############################################
|
||||||
|
# Build in Golang
|
||||||
|
# Run npm run build-healthcheck-armv7 in the host first, another it will be super slow where it is building the armv7 healthcheck
|
||||||
|
############################################
|
||||||
|
FROM golang:1.19-buster
|
||||||
|
WORKDIR /app
|
||||||
|
ARG TARGETPLATFORM
|
||||||
|
COPY ./extra/ ./extra/
|
||||||
|
|
||||||
|
# Compile healthcheck.go
|
||||||
|
RUN apt update && \
|
||||||
|
apt --yes --no-install-recommends install curl && \
|
||||||
|
curl -sL https://deb.nodesource.com/setup_18.x | bash && \
|
||||||
|
apt --yes --no-install-recommends install nodejs && \
|
||||||
|
node ./extra/build-healthcheck.js $TARGETPLATFORM && \
|
||||||
|
apt --yes remove nodejs
|
@@ -8,21 +8,21 @@ WORKDIR /app
|
|||||||
# Install Curl
|
# Install Curl
|
||||||
# Install Apprise, add sqlite3 cli for debugging in the future, iputils-ping for ping, util-linux for setpriv
|
# Install Apprise, add sqlite3 cli for debugging in the future, iputils-ping for ping, util-linux for setpriv
|
||||||
# Stupid python3 and python3-pip actually install a lot of useless things into Debian, specify --no-install-recommends to skip them, make the base even smaller than alpine!
|
# Stupid python3 and python3-pip actually install a lot of useless things into Debian, specify --no-install-recommends to skip them, make the base even smaller than alpine!
|
||||||
RUN apt update && \
|
RUN apt-get 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 \
|
apt-get --yes --no-install-recommends install python3 python3-pip python3-cryptography python3-six python3-yaml python3-click python3-markdown python3-requests python3-requests-oauthlib \
|
||||||
sqlite3 iputils-ping util-linux dumb-init && \
|
sqlite3 iputils-ping util-linux dumb-init git curl ca-certificates && \
|
||||||
pip3 --no-cache-dir install apprise==1.0.0 && \
|
pip3 --no-cache-dir install apprise==1.4.0 && \
|
||||||
rm -rf /var/lib/apt/lists/* && \
|
rm -rf /var/lib/apt/lists/* && \
|
||||||
apt --yes autoremove
|
apt --yes autoremove
|
||||||
|
|
||||||
# Install cloudflared
|
# Install cloudflared
|
||||||
# dpkg --add-architecture arm: cloudflared do not provide armhf, this is workaround. Read more: https://github.com/cloudflare/cloudflared/issues/583
|
RUN set -eux && \
|
||||||
COPY extra/download-cloudflared.js ./extra/download-cloudflared.js
|
mkdir -p --mode=0755 /usr/share/keyrings && \
|
||||||
RUN node ./extra/download-cloudflared.js $TARGETPLATFORM && \
|
curl --fail --show-error --silent --location --insecure https://pkg.cloudflare.com/cloudflare-main.gpg --output /usr/share/keyrings/cloudflare-main.gpg && \
|
||||||
dpkg --add-architecture arm && \
|
echo 'deb [signed-by=/usr/share/keyrings/cloudflare-main.gpg] https://pkg.cloudflare.com/cloudflared buster main' | tee /etc/apt/sources.list.d/cloudflared.list && \
|
||||||
apt update && \
|
apt-get update && \
|
||||||
apt --yes --no-install-recommends install ./cloudflared.deb && \
|
apt-get install --yes --no-install-recommends cloudflared && \
|
||||||
|
cloudflared version && \
|
||||||
rm -rf /var/lib/apt/lists/* && \
|
rm -rf /var/lib/apt/lists/* && \
|
||||||
rm -f cloudflared.deb && \
|
|
||||||
apt --yes autoremove
|
apt --yes autoremove
|
||||||
|
|
||||||
|
@@ -1,30 +1,50 @@
|
|||||||
|
############################################
|
||||||
|
# Build in Golang
|
||||||
|
# Run npm run build-healthcheck-armv7 in the host first, another it will be super slow where it is building the armv7 healthcheck
|
||||||
|
# Check file: builder-go.dockerfile
|
||||||
|
############################################
|
||||||
|
FROM louislam/uptime-kuma:builder-go AS build_healthcheck
|
||||||
|
|
||||||
|
############################################
|
||||||
|
# Build in Node.js
|
||||||
|
############################################
|
||||||
FROM louislam/uptime-kuma:base-debian AS build
|
FROM louislam/uptime-kuma:base-debian AS build
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=1
|
ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=1
|
||||||
|
COPY .npmrc .npmrc
|
||||||
|
COPY package.json package.json
|
||||||
|
COPY package-lock.json package-lock.json
|
||||||
|
RUN npm ci --omit=dev
|
||||||
COPY . .
|
COPY . .
|
||||||
RUN npm ci --production && \
|
COPY --from=build_healthcheck /app/extra/healthcheck /app/extra/healthcheck
|
||||||
chmod +x /app/extra/entrypoint.sh
|
RUN chmod +x /app/extra/entrypoint.sh
|
||||||
|
|
||||||
|
|
||||||
|
############################################
|
||||||
|
# ⭐ Main Image
|
||||||
|
############################################
|
||||||
FROM louislam/uptime-kuma:base-debian AS release
|
FROM louislam/uptime-kuma:base-debian AS release
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
# Copy app files from build layer
|
# Copy app files from build layer
|
||||||
COPY --from=build /app /app
|
COPY --from=build /app /app
|
||||||
|
|
||||||
|
|
||||||
EXPOSE 3001
|
EXPOSE 3001
|
||||||
VOLUME ["/app/data"]
|
VOLUME ["/app/data"]
|
||||||
HEALTHCHECK --interval=60s --timeout=30s --start-period=180s --retries=5 CMD node extra/healthcheck.js
|
HEALTHCHECK --interval=60s --timeout=30s --start-period=180s --retries=5 CMD extra/healthcheck
|
||||||
ENTRYPOINT ["/usr/bin/dumb-init", "--", "extra/entrypoint.sh"]
|
ENTRYPOINT ["/usr/bin/dumb-init", "--", "extra/entrypoint.sh"]
|
||||||
CMD ["node", "server/server.js"]
|
CMD ["node", "server/server.js"]
|
||||||
|
|
||||||
|
############################################
|
||||||
|
# Mark as Nightly
|
||||||
|
############################################
|
||||||
FROM release AS nightly
|
FROM release AS nightly
|
||||||
RUN npm run mark-as-nightly
|
RUN npm run mark-as-nightly
|
||||||
|
|
||||||
|
############################################
|
||||||
# Build an image for testing pr
|
# Build an image for testing pr
|
||||||
|
############################################
|
||||||
FROM louislam/uptime-kuma:base-debian AS pr-test
|
FROM louislam/uptime-kuma:base-debian AS pr-test
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
@@ -51,11 +71,12 @@ RUN npm ci
|
|||||||
|
|
||||||
EXPOSE 3000 3001
|
EXPOSE 3000 3001
|
||||||
VOLUME ["/app/data"]
|
VOLUME ["/app/data"]
|
||||||
HEALTHCHECK --interval=60s --timeout=30s --start-period=180s --retries=5 CMD node extra/healthcheck.js
|
HEALTHCHECK --interval=60s --timeout=30s --start-period=180s --retries=5 CMD extra/healthcheck
|
||||||
CMD ["npm", "run", "start-pr-test"]
|
CMD ["npm", "run", "start-pr-test"]
|
||||||
|
|
||||||
|
############################################
|
||||||
# Upload the artifact to Github
|
# Upload the artifact to Github
|
||||||
|
############################################
|
||||||
FROM louislam/uptime-kuma:base-debian AS upload-artifact
|
FROM louislam/uptime-kuma:base-debian AS upload-artifact
|
||||||
WORKDIR /
|
WORKDIR /
|
||||||
RUN apt update && \
|
RUN apt update && \
|
||||||
|
@@ -3,10 +3,12 @@ WORKDIR /app
|
|||||||
|
|
||||||
ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=1
|
ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=1
|
||||||
|
|
||||||
|
COPY .npmrc .npmrc
|
||||||
|
COPY package.json package.json
|
||||||
|
COPY package-lock.json package-lock.json
|
||||||
|
RUN npm ci --omit=dev
|
||||||
COPY . .
|
COPY . .
|
||||||
RUN npm ci --production && \
|
RUN chmod +x /app/extra/entrypoint.sh
|
||||||
chmod +x /app/extra/entrypoint.sh
|
|
||||||
|
|
||||||
|
|
||||||
FROM louislam/uptime-kuma:base-alpine AS release
|
FROM louislam/uptime-kuma:base-alpine AS release
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
@@ -22,7 +22,8 @@ if (! exists) {
|
|||||||
fs.writeFileSync("package.json", JSON.stringify(pkg, null, 4) + "\n");
|
fs.writeFileSync("package.json", JSON.stringify(pkg, null, 4) + "\n");
|
||||||
|
|
||||||
// Also update package-lock.json
|
// Also update package-lock.json
|
||||||
childProcess.spawnSync("npm", [ "install" ]);
|
const npm = /^win/.test(process.platform) ? "npm.cmd" : "npm";
|
||||||
|
childProcess.spawnSync(npm, [ "install" ]);
|
||||||
|
|
||||||
commit(version);
|
commit(version);
|
||||||
tag(version);
|
tag(version);
|
||||||
@@ -32,6 +33,10 @@ if (! exists) {
|
|||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Commit updated files
|
||||||
|
* @param {string} version Version to update to
|
||||||
|
*/
|
||||||
function commit(version) {
|
function commit(version) {
|
||||||
let msg = "Update to " + version;
|
let msg = "Update to " + version;
|
||||||
|
|
||||||
@@ -47,6 +52,10 @@ function commit(version) {
|
|||||||
console.log(res.stdout.toString().trim());
|
console.log(res.stdout.toString().trim());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a tag with the specified version
|
||||||
|
* @param {string} version Tag to create
|
||||||
|
*/
|
||||||
function tag(version) {
|
function tag(version) {
|
||||||
let res = childProcess.spawnSync("git", [ "tag", version ]);
|
let res = childProcess.spawnSync("git", [ "tag", version ]);
|
||||||
console.log(res.stdout.toString().trim());
|
console.log(res.stdout.toString().trim());
|
||||||
@@ -55,6 +64,11 @@ function tag(version) {
|
|||||||
console.log(res.stdout.toString().trim());
|
console.log(res.stdout.toString().trim());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a tag exists for the specified version
|
||||||
|
* @param {string} version Version to check
|
||||||
|
* @returns {boolean} Does the tag already exist
|
||||||
|
*/
|
||||||
function tagExists(version) {
|
function tagExists(version) {
|
||||||
if (! version) {
|
if (! version) {
|
||||||
throw new Error("invalid version");
|
throw new Error("invalid version");
|
||||||
|
27
extra/build-healthcheck.js
Normal file
27
extra/build-healthcheck.js
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
const childProcess = require("child_process");
|
||||||
|
const fs = require("fs");
|
||||||
|
const platform = process.argv[2];
|
||||||
|
|
||||||
|
if (!platform) {
|
||||||
|
console.error("No platform??");
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (platform === "linux/arm/v7") {
|
||||||
|
console.log("Arch: armv7");
|
||||||
|
if (fs.existsSync("./extra/healthcheck-armv7")) {
|
||||||
|
fs.renameSync("./extra/healthcheck-armv7", "./extra/healthcheck");
|
||||||
|
console.log("Already built in the host, skip.");
|
||||||
|
process.exit(0);
|
||||||
|
} else {
|
||||||
|
console.log("prebuilt not found, it will be slow! You should execute `npm run build-healthcheck-armv7` before build.");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (fs.existsSync("./extra/healthcheck-armv7")) {
|
||||||
|
fs.rmSync("./extra/healthcheck-armv7");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const output = childProcess.execSync("go build -x -o ./extra/healthcheck ./extra/healthcheck.go").toString("utf8");
|
||||||
|
console.log(output);
|
||||||
|
|
60
extra/deploy-demo-server.js
Normal file
60
extra/deploy-demo-server.js
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
require("dotenv").config();
|
||||||
|
const { NodeSSH } = require("node-ssh");
|
||||||
|
const readline = require("readline");
|
||||||
|
const rl = readline.createInterface({ input: process.stdin,
|
||||||
|
output: process.stdout });
|
||||||
|
const prompt = (query) => new Promise((resolve) => rl.question(query, resolve));
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
try {
|
||||||
|
console.log("SSH to demo server");
|
||||||
|
const ssh = new NodeSSH();
|
||||||
|
await ssh.connect({
|
||||||
|
host: process.env.UPTIME_KUMA_DEMO_HOST,
|
||||||
|
port: process.env.UPTIME_KUMA_DEMO_PORT,
|
||||||
|
username: process.env.UPTIME_KUMA_DEMO_USERNAME,
|
||||||
|
privateKeyPath: process.env.UPTIME_KUMA_DEMO_PRIVATE_KEY_PATH
|
||||||
|
});
|
||||||
|
|
||||||
|
let cwd = process.env.UPTIME_KUMA_DEMO_CWD;
|
||||||
|
let result;
|
||||||
|
|
||||||
|
const version = await prompt("Enter Version: ");
|
||||||
|
|
||||||
|
result = await ssh.execCommand("git fetch --all", {
|
||||||
|
cwd,
|
||||||
|
});
|
||||||
|
console.log(result.stdout + result.stderr);
|
||||||
|
|
||||||
|
await prompt("Press any key to continue...");
|
||||||
|
|
||||||
|
result = await ssh.execCommand(`git checkout ${version} --force`, {
|
||||||
|
cwd,
|
||||||
|
});
|
||||||
|
console.log(result.stdout + result.stderr);
|
||||||
|
|
||||||
|
result = await ssh.execCommand("npm run download-dist", {
|
||||||
|
cwd,
|
||||||
|
});
|
||||||
|
console.log(result.stdout + result.stderr);
|
||||||
|
|
||||||
|
result = await ssh.execCommand("npm install --production", {
|
||||||
|
cwd,
|
||||||
|
});
|
||||||
|
console.log(result.stdout + result.stderr);
|
||||||
|
|
||||||
|
/*
|
||||||
|
result = await ssh.execCommand("pm2 restart 1", {
|
||||||
|
cwd,
|
||||||
|
});
|
||||||
|
console.log(result.stdout + result.stderr);*/
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e);
|
||||||
|
} finally {
|
||||||
|
rl.close();
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
// When done reading prompt, exit program
|
||||||
|
rl.on("close", () => process.exit(0));
|
@@ -1,44 +0,0 @@
|
|||||||
//
|
|
||||||
|
|
||||||
const http = require("https"); // or 'https' for https:// URLs
|
|
||||||
const fs = require("fs");
|
|
||||||
|
|
||||||
const platform = process.argv[2];
|
|
||||||
|
|
||||||
if (!platform) {
|
|
||||||
console.error("No platform??");
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
let arch = null;
|
|
||||||
|
|
||||||
if (platform === "linux/amd64") {
|
|
||||||
arch = "amd64";
|
|
||||||
} else if (platform === "linux/arm64") {
|
|
||||||
arch = "arm64";
|
|
||||||
} else if (platform === "linux/arm/v7") {
|
|
||||||
arch = "arm";
|
|
||||||
} else {
|
|
||||||
console.error("Invalid platform?? " + platform);
|
|
||||||
}
|
|
||||||
|
|
||||||
const file = fs.createWriteStream("cloudflared.deb");
|
|
||||||
get("https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-" + arch + ".deb");
|
|
||||||
|
|
||||||
function get(url) {
|
|
||||||
http.get(url, function (res) {
|
|
||||||
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
||||||
console.log("Redirect to " + res.headers.location);
|
|
||||||
get(res.headers.location);
|
|
||||||
} else if (res.statusCode >= 200 && res.statusCode < 300) {
|
|
||||||
res.pipe(file);
|
|
||||||
|
|
||||||
res.on("end", function () {
|
|
||||||
console.log("Downloaded");
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
console.error(res.statusCode);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
@@ -47,6 +47,7 @@ function download(url) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
console.log("Done");
|
console.log("Done");
|
||||||
|
process.exit(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
tarStream.on("error", () => {
|
tarStream.on("error", () => {
|
||||||
|
1
extra/exe-builder/.gitignore
vendored
Normal file
1
extra/exe-builder/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
packages/
|
35
extra/exe-builder/App.config
Normal file
35
extra/exe-builder/App.config
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<configuration>
|
||||||
|
<startup>
|
||||||
|
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" />
|
||||||
|
</startup>
|
||||||
|
|
||||||
|
<runtime>
|
||||||
|
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||||
|
<dependentAssembly>
|
||||||
|
<assemblyIdentity name="System.Diagnostics.DiagnosticSource" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
|
||||||
|
<bindingRedirect oldVersion="0.0.0.0-4.0.1.0" newVersion="4.0.1.0" />
|
||||||
|
</dependentAssembly>
|
||||||
|
<dependentAssembly>
|
||||||
|
<assemblyIdentity name="System.Diagnostics.Tracing" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
|
||||||
|
<bindingRedirect oldVersion="0.0.0.0-4.1.1.0" newVersion="4.1.1.0" />
|
||||||
|
</dependentAssembly>
|
||||||
|
<dependentAssembly>
|
||||||
|
<assemblyIdentity name="System.Reflection" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
|
||||||
|
<bindingRedirect oldVersion="0.0.0.0-4.1.1.0" newVersion="4.1.1.0" />
|
||||||
|
</dependentAssembly>
|
||||||
|
<dependentAssembly>
|
||||||
|
<assemblyIdentity name="System.Runtime" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
|
||||||
|
<bindingRedirect oldVersion="0.0.0.0-4.1.1.1" newVersion="4.1.1.1" />
|
||||||
|
</dependentAssembly>
|
||||||
|
<dependentAssembly>
|
||||||
|
<assemblyIdentity name="System.Runtime.InteropServices" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
|
||||||
|
<bindingRedirect oldVersion="0.0.0.0-4.1.1.0" newVersion="4.1.1.0" />
|
||||||
|
</dependentAssembly>
|
||||||
|
<dependentAssembly>
|
||||||
|
<assemblyIdentity name="System.Runtime.CompilerServices.Unsafe" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
|
||||||
|
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0" />
|
||||||
|
</dependentAssembly>
|
||||||
|
</assemblyBinding>
|
||||||
|
</runtime>
|
||||||
|
</configuration>
|
84
extra/exe-builder/DownloadForm.Designer.cs
generated
Normal file
84
extra/exe-builder/DownloadForm.Designer.cs
generated
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
using System.ComponentModel;
|
||||||
|
|
||||||
|
namespace UptimeKuma {
|
||||||
|
partial class DownloadForm {
|
||||||
|
/// <summary>
|
||||||
|
/// Required designer variable.
|
||||||
|
/// </summary>
|
||||||
|
private IContainer components = null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Clean up any resources being used.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
|
||||||
|
protected override void Dispose(bool disposing) {
|
||||||
|
if (disposing && (components != null)) {
|
||||||
|
components.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
base.Dispose(disposing);
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Windows Form Designer generated code
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Required method for Designer support - do not modify
|
||||||
|
/// the contents of this method with the code editor.
|
||||||
|
/// </summary>
|
||||||
|
private void InitializeComponent() {
|
||||||
|
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(DownloadForm));
|
||||||
|
this.progressBar = new System.Windows.Forms.ProgressBar();
|
||||||
|
this.label = new System.Windows.Forms.Label();
|
||||||
|
this.labelData = new System.Windows.Forms.Label();
|
||||||
|
this.SuspendLayout();
|
||||||
|
//
|
||||||
|
// progressBar
|
||||||
|
//
|
||||||
|
this.progressBar.Location = new System.Drawing.Point(12, 12);
|
||||||
|
this.progressBar.Name = "progressBar";
|
||||||
|
this.progressBar.Size = new System.Drawing.Size(472, 41);
|
||||||
|
this.progressBar.TabIndex = 0;
|
||||||
|
//
|
||||||
|
// label
|
||||||
|
//
|
||||||
|
this.label.Location = new System.Drawing.Point(12, 59);
|
||||||
|
this.label.Name = "label";
|
||||||
|
this.label.Size = new System.Drawing.Size(472, 23);
|
||||||
|
this.label.TabIndex = 1;
|
||||||
|
this.label.Text = "Preparing...";
|
||||||
|
//
|
||||||
|
// labelData
|
||||||
|
//
|
||||||
|
this.labelData.Location = new System.Drawing.Point(12, 82);
|
||||||
|
this.labelData.Name = "labelData";
|
||||||
|
this.labelData.Size = new System.Drawing.Size(472, 23);
|
||||||
|
this.labelData.TabIndex = 2;
|
||||||
|
//
|
||||||
|
// DownloadForm
|
||||||
|
//
|
||||||
|
this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 16F);
|
||||||
|
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||||
|
this.ClientSize = new System.Drawing.Size(496, 117);
|
||||||
|
this.Controls.Add(this.labelData);
|
||||||
|
this.Controls.Add(this.label);
|
||||||
|
this.Controls.Add(this.progressBar);
|
||||||
|
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
|
||||||
|
this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon")));
|
||||||
|
this.MaximizeBox = false;
|
||||||
|
this.Name = "DownloadForm";
|
||||||
|
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
|
||||||
|
this.Text = "Uptime Kuma";
|
||||||
|
this.Load += new System.EventHandler(this.DownloadForm_Load);
|
||||||
|
this.ResumeLayout(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private System.Windows.Forms.Label labelData;
|
||||||
|
|
||||||
|
private System.Windows.Forms.Label label;
|
||||||
|
|
||||||
|
private System.Windows.Forms.ProgressBar progressBar;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
204
extra/exe-builder/DownloadForm.cs
Normal file
204
extra/exe-builder/DownloadForm.cs
Normal file
@@ -0,0 +1,204 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
|
using System.IO.Compression;
|
||||||
|
using System.Net;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace UptimeKuma {
|
||||||
|
public partial class DownloadForm : Form {
|
||||||
|
private readonly Queue<DownloadItem> downloadQueue = new();
|
||||||
|
private readonly WebClient webClient = new();
|
||||||
|
private DownloadItem currentDownloadItem;
|
||||||
|
|
||||||
|
public DownloadForm() {
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DownloadForm_Load(object sender, EventArgs e) {
|
||||||
|
webClient.DownloadProgressChanged += DownloadProgressChanged;
|
||||||
|
webClient.DownloadFileCompleted += DownloadFileCompleted;
|
||||||
|
|
||||||
|
label.Text = "Reading latest version...";
|
||||||
|
|
||||||
|
// Read json from https://uptime.kuma.pet/version
|
||||||
|
var versionJson = new WebClient().DownloadString("https://uptime.kuma.pet/version");
|
||||||
|
var versionObj = JsonConvert.DeserializeObject<Version>(versionJson);
|
||||||
|
|
||||||
|
var nodeVersion = versionObj.nodejs;
|
||||||
|
var uptimeKumaVersion = versionObj.latest;
|
||||||
|
var hasUpdateFile = File.Exists("update");
|
||||||
|
|
||||||
|
if (!Directory.Exists("node")) {
|
||||||
|
downloadQueue.Enqueue(new DownloadItem {
|
||||||
|
URL = $"https://nodejs.org/dist/v{nodeVersion}/node-v{nodeVersion}-win-x64.zip",
|
||||||
|
Filename = "node.zip",
|
||||||
|
TargetFolder = "node"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Directory.Exists("core") || hasUpdateFile) {
|
||||||
|
|
||||||
|
// It is update, rename the core folder to core.old
|
||||||
|
if (Directory.Exists("core")) {
|
||||||
|
// Remove the old core.old folder
|
||||||
|
if (Directory.Exists("core.old")) {
|
||||||
|
Directory.Delete("core.old", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
Directory.Move("core", "core.old");
|
||||||
|
}
|
||||||
|
|
||||||
|
downloadQueue.Enqueue(new DownloadItem {
|
||||||
|
URL = $"https://github.com/louislam/uptime-kuma/archive/refs/tags/{uptimeKumaVersion}.zip",
|
||||||
|
Filename = "core.zip",
|
||||||
|
TargetFolder = "core"
|
||||||
|
});
|
||||||
|
|
||||||
|
File.WriteAllText("version.json", versionJson);
|
||||||
|
|
||||||
|
// Delete the update file
|
||||||
|
if (hasUpdateFile) {
|
||||||
|
File.Delete("update");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DownloadNextFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DownloadNextFile() {
|
||||||
|
if (downloadQueue.Count > 0) {
|
||||||
|
var item = downloadQueue.Dequeue();
|
||||||
|
|
||||||
|
currentDownloadItem = item;
|
||||||
|
|
||||||
|
// Download if the zip file is not existing
|
||||||
|
if (!File.Exists(item.Filename)) {
|
||||||
|
label.Text = item.URL;
|
||||||
|
webClient.DownloadFileAsync(new Uri(item.URL), item.Filename);
|
||||||
|
} else {
|
||||||
|
progressBar.Value = 100;
|
||||||
|
label.Text = "Use local " + item.Filename;
|
||||||
|
DownloadFileCompleted(null, null);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
npmSetup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void npmSetup() {
|
||||||
|
labelData.Text = "";
|
||||||
|
|
||||||
|
var npm = "..\\node\\npm.cmd";
|
||||||
|
var cmd = $"{npm} ci --production & {npm} run download-dist & exit";
|
||||||
|
|
||||||
|
var startInfo = new ProcessStartInfo {
|
||||||
|
FileName = "cmd.exe",
|
||||||
|
Arguments = $"/k \"{cmd}\"",
|
||||||
|
RedirectStandardOutput = false,
|
||||||
|
RedirectStandardError = false,
|
||||||
|
RedirectStandardInput = true,
|
||||||
|
UseShellExecute = false,
|
||||||
|
CreateNoWindow = false,
|
||||||
|
WorkingDirectory = "core"
|
||||||
|
};
|
||||||
|
|
||||||
|
var process = new Process();
|
||||||
|
process.StartInfo = startInfo;
|
||||||
|
process.EnableRaisingEvents = true;
|
||||||
|
process.Exited += (_, e) => {
|
||||||
|
progressBar.Value = 100;
|
||||||
|
|
||||||
|
if (process.ExitCode == 0) {
|
||||||
|
Task.Delay(2000).ContinueWith(_ => {
|
||||||
|
Application.Restart();
|
||||||
|
});
|
||||||
|
label.Text = "Done";
|
||||||
|
} else {
|
||||||
|
label.Text = "Failed, exit code: " + process.ExitCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
process.Start();
|
||||||
|
label.Text = "Installing dependencies and download dist files";
|
||||||
|
progressBar.Value = 50;
|
||||||
|
process.WaitForExit();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e) {
|
||||||
|
progressBar.Value = e.ProgressPercentage;
|
||||||
|
var total = e.TotalBytesToReceive / 1024;
|
||||||
|
var current = e.BytesReceived / 1024;
|
||||||
|
|
||||||
|
if (total > 0) {
|
||||||
|
labelData.Text = $"{current}KB/{total}KB";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DownloadFileCompleted(object sender, AsyncCompletedEventArgs e) {
|
||||||
|
Extract(currentDownloadItem);
|
||||||
|
DownloadNextFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Extract(DownloadItem item) {
|
||||||
|
if (Directory.Exists(item.TargetFolder)) {
|
||||||
|
var dir = new DirectoryInfo(item.TargetFolder);
|
||||||
|
dir.Delete(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Directory.Exists("temp")) {
|
||||||
|
var dir = new DirectoryInfo("temp");
|
||||||
|
dir.Delete(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
labelData.Text = $"Extracting {item.Filename}...";
|
||||||
|
|
||||||
|
ZipFile.ExtractToDirectory(item.Filename, "temp");
|
||||||
|
|
||||||
|
string[] dirList;
|
||||||
|
|
||||||
|
// Move to the correct level
|
||||||
|
dirList = Directory.GetDirectories("temp");
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if (dirList.Length > 0) {
|
||||||
|
var dir = dirList[0];
|
||||||
|
|
||||||
|
// As sometime ExtractToDirectory is still locking the directory, loop until ok
|
||||||
|
while (true) {
|
||||||
|
try {
|
||||||
|
Directory.Move(dir, item.TargetFolder);
|
||||||
|
break;
|
||||||
|
} catch (Exception exception) {
|
||||||
|
Thread.Sleep(1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
MessageBox.Show("Unexcepted Error: Cannot move extracted files, folder not found.");
|
||||||
|
}
|
||||||
|
|
||||||
|
labelData.Text = $"Extracted";
|
||||||
|
|
||||||
|
if (Directory.Exists("temp")) {
|
||||||
|
var dir = new DirectoryInfo("temp");
|
||||||
|
dir.Delete(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
File.Delete(item.Filename);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class DownloadItem {
|
||||||
|
public string URL { get; set; }
|
||||||
|
public string Filename { get; set; }
|
||||||
|
public string TargetFolder { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
377
extra/exe-builder/DownloadForm.resx
Normal file
377
extra/exe-builder/DownloadForm.resx
Normal file
@@ -0,0 +1,377 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<root>
|
||||||
|
<!--
|
||||||
|
Microsoft ResX Schema
|
||||||
|
|
||||||
|
Version 2.0
|
||||||
|
|
||||||
|
The primary goals of this format is to allow a simple XML format
|
||||||
|
that is mostly human readable. The generation and parsing of the
|
||||||
|
various data types are done through the TypeConverter classes
|
||||||
|
associated with the data types.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
... ado.net/XML headers & schema ...
|
||||||
|
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||||
|
<resheader name="version">2.0</resheader>
|
||||||
|
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||||
|
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||||
|
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||||
|
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||||
|
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||||
|
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||||
|
</data>
|
||||||
|
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||||
|
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||||
|
<comment>This is a comment</comment>
|
||||||
|
</data>
|
||||||
|
|
||||||
|
There are any number of "resheader" rows that contain simple
|
||||||
|
name/value pairs.
|
||||||
|
|
||||||
|
Each data row contains a name, and value. The row also contains a
|
||||||
|
type or mimetype. Type corresponds to a .NET class that support
|
||||||
|
text/value conversion through the TypeConverter architecture.
|
||||||
|
Classes that don't support this are serialized and stored with the
|
||||||
|
mimetype set.
|
||||||
|
|
||||||
|
The mimetype is used for serialized objects, and tells the
|
||||||
|
ResXResourceReader how to depersist the object. This is currently not
|
||||||
|
extensible. For a given mimetype the value must be set accordingly:
|
||||||
|
|
||||||
|
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||||
|
that the ResXResourceWriter will generate, however the reader can
|
||||||
|
read any of the formats listed below.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.binary.base64
|
||||||
|
value : The object must be serialized with
|
||||||
|
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.soap.base64
|
||||||
|
value : The object must be serialized with
|
||||||
|
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||||
|
value : The object must be serialized into a byte array
|
||||||
|
: using a System.ComponentModel.TypeConverter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
-->
|
||||||
|
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||||
|
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||||
|
<xsd:element name="root" msdata:IsDataSet="true">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:choice maxOccurs="unbounded">
|
||||||
|
<xsd:element name="metadata">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||||
|
<xsd:attribute name="type" type="xsd:string" />
|
||||||
|
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||||
|
<xsd:attribute ref="xml:space" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="assembly">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:attribute name="alias" type="xsd:string" />
|
||||||
|
<xsd:attribute name="name" type="xsd:string" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="data">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||||
|
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||||
|
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||||
|
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||||
|
<xsd:attribute ref="xml:space" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="resheader">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
</xsd:choice>
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
</xsd:schema>
|
||||||
|
<resheader name="resmimetype">
|
||||||
|
<value>text/microsoft-resx</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="version">
|
||||||
|
<value>2.0</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="reader">
|
||||||
|
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="writer">
|
||||||
|
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
|
</resheader>
|
||||||
|
<assembly alias="System.Drawing" name="System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
|
||||||
|
<data name="$this.Icon" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||||
|
<value>
|
||||||
|
AAABAAMAMDAAAAEAIACoJQAANgAAACAgAAABACAAqBAAAN4lAAAQEAAAAQAgAGgEAACGNgAAKAAAADAA
|
||||||
|
AABgAAAAAQAgAAAAAAAAJAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||||
|
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||||
|
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||||
|
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||||
|
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||||
|
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||||
|
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||||
|
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||||
|
AAAAAAAA////BPT09Bfu7u4e8fHxJPPz8yv19fUy9fX1M/Pz8yvx8fEk9vb2HPPz8xXMzMwFAAAAAAAA
|
||||||
|
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||||
|
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP//
|
||||||
|
/wHv7+8f7u7uPPPz81Tx8fFs8fHxgPHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx
|
||||||
|
8YLx8fGB8fHxcfHx8V3x8fFI9PT0MOvr6w0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||||
|
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||||
|
AADy8vIU8fHxS/Dw8Hbx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx
|
||||||
|
8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fFr9PT0R/Dw8CIAAAABAAAAAAAA
|
||||||
|
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||||
|
AAAAAAAA8vLyFPHx8Vnx8fGB8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx
|
||||||
|
8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx
|
||||||
|
8YLx8fFs9fX1Mb+/vwQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||||
|
AAAAAAAAAAAAAICAgALy8vI88fHxfvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx
|
||||||
|
8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx
|
||||||
|
8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvLy8nby8vI8gICAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||||
|
AAAAAAAAAAAAAAAAAAAAAAAAzMzMBfHx8Vrx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx
|
||||||
|
8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx
|
||||||
|
8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8vLyYf///wwAAAAAAAAAAAAA
|
||||||
|
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMzMwF8vLyYPHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx
|
||||||
|
8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx
|
||||||
|
8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx
|
||||||
|
8W/z8/MWAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADv7+9R8fHxgvHx8YLx8fGC8fHxgvHx
|
||||||
|
8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx
|
||||||
|
8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx
|
||||||
|
8YLx8fGC8fHxgvHx8YLw8PB26urqDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPLy8ijx8fGC8fHxgvHx
|
||||||
|
8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgu7w7Ifj79ud2u7PtNLrw83P677dzeu85c3r
|
||||||
|
u+rM67rwzOu68c7rverQ68Dj0uvD3NbuyM3b7c+64u7apujv5ZPx8fGC8fHxgvHx8YLx8fGC8fHxgvHx
|
||||||
|
8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxXgAAAAEAAAAAAAAAAAAAAAAAAAAA4+PjCfDw
|
||||||
|
8Hfx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLd7tSmzeu92MbqsvvG6bH/xumy/8fq
|
||||||
|
s//H6rP/yOq0/8jqtf/J6rb/yeq2/8rrt//K67j/y+u4/8vruf/M67r/zOu7/83ru//Q7MDx1u7Kz9/t
|
||||||
|
163s8OuJ8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgu/v7y8AAAAAAAAAAAAA
|
||||||
|
AAAAAAAA7u7uPfHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC5PDdl8jqtuTE6a7/xOmv/8Xp
|
||||||
|
sP/G6bH/xumx/8bpsv/H6rP/x+qz/8jqtP/I6rX/yeq2/8nqtv/K67f/yuu4/8vruP/L67n/zOu6/8zr
|
||||||
|
u//N67v/zey8/87svf/P67742e3Mx+jv5ZLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvDw
|
||||||
|
8HWAgIACAAAAAAAAAACqqqoD8vLyc/Hx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLf7degxOiu+cPo
|
||||||
|
rf/D6a7/xOmu/8Xpr//F6bD/xumx/8bpsf/G6bL/x+qz/8fqs//I6rT/yOq1/8nqtv/J6rb/yuu3/8rr
|
||||||
|
uP/L67j/y+u5/8zruv/M67v/zeu7/83svP/O7L3/zuy9/87svfzc7tK28fHxgvHx8YLx8fGC8fHxgvHx
|
||||||
|
8YLx8fGC8fHxgvHx8YLx8fEkAAAAAAAAAADz8/Mq8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgunv
|
||||||
|
5o3D6a/0wuis/8Lorf/D6K3/xOmu/8Tprv/F6a//xemw/8bpsf/G6bH/xumy/8fqs//H6rP/yOq0/8jq
|
||||||
|
tf/J6rb/yeq2/8rrt//K67j/y+u4/8vruf/M67r/zOu7/83ru//N7Lz/zuy9/87svf/O7L3/3e/TtPHx
|
||||||
|
8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLy8vJNAAAAAAAAAADy8vJM8fHxgvHx8YLx8fGC8fHxgvHx
|
||||||
|
8YLx8fGC8fHxgszqutDB6Kv/weir/8LorP/D6K3/w+it/8Tprv/E6a7/xemv/8XpsP/G6bH/xumx/8bp
|
||||||
|
sv/H6rP/x+qz/8jqtP/I6rX/yeq2/8nqtv/K67f/yuu4/8vruP/L67n/zOu6/8zru//N67v/zey8/87s
|
||||||
|
vf/O7L3/zuy++u3w6Yzx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLy8vJ1AAAAAAAAAADx8fFr8fHxgvHx
|
||||||
|
8YLx8fGC8fHxgvHx8YLx8fGC6O/kjsDoqvzA6Kr/weir/8Loq//C6Kz/w+it/8Porf/E6a7/xOmu/8Xp
|
||||||
|
r//F6bD/xumx/8bpsf/G6bL/x+qz/8fqtP/I6rT/yOq1/8nqtv/J6rb/yuu3/8rruP/L67n/y+u5/8zr
|
||||||
|
uv/M67v/zeu7/83svP/O7L3/zuy9/93u07Xx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC////Bv//
|
||||||
|
/wfx8fGB8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC1ezJsr/nqf/A56n/weiq/8Hoq//C6Kv/wuis/8Po
|
||||||
|
rf/D6K3/xOmu/8Pprv+856T/uOed/7bmmv+05Zf/teWZ/7jnnf+86KP/wOio/8fqs//J6rb/yeq2/8rr
|
||||||
|
t//K67j/y+u5/8vruf/M67r/zOu7/83ru//N7Lz/zuy9/9buyNLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx
|
||||||
|
8YLx8fGC8vLyE/Ly8hPx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGCy+q6zr/nqP/A56n/wOep/8Ho
|
||||||
|
qv/B6Kv/wuir/8LorP+u5Y//neF2/5bgav+V4Gr/luBr/5fhbP+Y4W7/meFv/5rhcf+b4nL/nOJ0/53i
|
||||||
|
dv+j5H//reaM/7nnnf/E6q//y+y4/8vruf/L67n/zOu6/8zru//N67v/zey8/9Lsxd/x8fGC8fHxgvHx
|
||||||
|
8YLx8fGC8fHxgvHx8YLx8fGC7+/vIPb29hzx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGCx+m03L/n
|
||||||
|
qP+/56j/wOep/8Dnqf/B6Kr/weir/7nmn/+R32T/kt9l/5PfZ/+U4Gj/leBq/5bga/+X4W3/mOFu/5nh
|
||||||
|
b/+a4XH/m+Jy/5zidP+d4nX/nuN3/5/jeP+f4nn/weqq/8rruP/L67n/y+u5/8zruv/M67v/zeu7/9Ls
|
||||||
|
w+Lx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8PDwI/Hx8SXx8fGC8fHxgvHx8YLx8fGC8fHxgvHx
|
||||||
|
8YLx8fGCxeix5L/nqP+/56j/v+eo/8Dnqf/A56n/weiq/7Pllv+Q3mP/kd9k/5LfZf+T32f/lOBo/5Xg
|
||||||
|
av+W4Gv/l+Ft/5jhbv+Z4W//muFx/5vicv+c4nT/neJ1/57jd/+f43j/xOmu/8rrt//K67j/y+u5/8vr
|
||||||
|
uf/M67r/zOu7/9Tsxtfx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC9PT0GO/v7yDx8fGC8fHxgvHx
|
||||||
|
8YLx8fGC8fHxgvHx8YLx8fGCx+m037/nqP+/56j/v+eo/7/nqP/A56n/wOip/7TmmP+P3mH/kN5j/5Hf
|
||||||
|
ZP+S32b/k99n/5TgaP+V4Gr/luBr/5fhbf+Y4W7/meFw/5rhcf+b4nL/nOJ0/53idf+h5Hz/yuu2/8nq
|
||||||
|
t//K67f/yuu4/8vruf/L67n/zOu6/9ftysrx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC7e3tDvT0
|
||||||
|
9Bfx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGCyOq117/nqP+/56j/v+eo/7/nqP+/56j/wOep/7vn
|
||||||
|
of+O3mD/j95h/5DeY/+R32T/kt9m/5PfZ/+U4Gj/leBq/5bga/+X4W3/mOFu/5nhcP+a4nH/m+Jy/5zi
|
||||||
|
dP+r5Yr/yOq1/8nqtv/J6rf/yuu3/8rruP/L67n/y+u5/9zu1LHx8fGC8fHxgvHx8YLx8fGC8fHxgvHx
|
||||||
|
8YLz8/OA////A+7u7g/x8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGCz+q+xb/nqP+/56j/v+eo/7/n
|
||||||
|
qP+/56j/v+eo/8Dnqf+S4Gb/jt5g/4/eYf+Q3mP/kd9k/5LfZv+T32f/lOBo/5Xgav+W4Gv/l+Ft/5jh
|
||||||
|
bv+Z4XD/muJx/5vic/+4553/yOq0/8jqtf/J6rb/yeq3/8rrt//K67j/y+u5/+bw4Zfx8fGC8fHxgvHx
|
||||||
|
8YLx8fGC8fHxgvHx8YLx8fFrAAAAAP///wHz8/N88fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC1+zMrr/n
|
||||||
|
qP+/56j/v+eo/7/nqP+/56j/v+eo/7/nqP+f4Xn/jd5f/47eYP+P3mH/kN5j/5HfZP+S32b/k99n/5Tg
|
||||||
|
af+V4Gr/luBr/5fhbf+Y4W7/meFw/5vic//F6rD/x+q0/8jqtP/I6rX/yeq2/8nqt//K67f/zOu88u/x
|
||||||
|
74Px8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLv7+9QAAAAAAAAAADw8PBm8fHxgvHx8YLx8fGC8fHxgvHx
|
||||||
|
8YLx8fGC5e7gk7/nqP+/56j/v+eo/7/nqP+/56j/v+eo/7/nqP+u5I//jN1d/43eX/+O3mD/j95h/5De
|
||||||
|
Y/+R32T/kt9m/5PfZ/+U4Gn/leBq/5bga/+X4W3/mOFu/6rliP/G6rL/x+qz/8fqtP/I6rT/yOq1/8nq
|
||||||
|
tv/J6rf/1OzGy/Hx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YL19fUzAAAAAAAAAADy8vJO8fHxgvHx
|
||||||
|
8YLx8fGC8fHxgvHx8YLx8fGC8fHxgsPoru2/56j/v+eo/7/nqP+/56j/v+eo/7/nqP++6Kf/j95i/4zd
|
||||||
|
Xf+N3l//jt5g/4/eYv+Q3mP/kd9k/5LfZv+T32f/lOBp/5Xgav+W4Gz/l+Ft/7voov/G6bL/xuqy/8fq
|
||||||
|
s//H6rT/yOq1/8jqtf/J6rb/4e/Zo/Hx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLw8PARAAAAAAAA
|
||||||
|
AADu7u4u8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgszpvMm/56j/v+eo/7/nqP+/56j/v+eo/7/n
|
||||||
|
qP+/56j/q+SL/4vdXP+M3V3/jd5f/47eYP+P3mL/kN9j/5HfZP+S32b/k99n/5Tgaf+V4Gr/qOOH/8Xp
|
||||||
|
sP/G6bH/xumy/8bqsv/H6rP/x+q0/8jqtf/K67jy8PHwhPHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx
|
||||||
|
8WoAAAAAAAAAAAAAAADo6OgL8fHxgfHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxguDv2J2/56j/v+eo/7/n
|
||||||
|
qP+/56j/v+eo/7/nqP+/56j/v+eo/6Xjgv+L3Vz/jN1d/43eX/+O3mD/j95i/5DfY/+R32T/kt9m/5Pf
|
||||||
|
Z/+k44D/xOmu/8XpsP/F6bD/xumx/8bpsv/G6rL/x+qz/8fqtP/W7cnB8fHxgvHx8YLx8fGC8fHxgvHx
|
||||||
|
8YLx8fGC8fHxgvPz80AAAAAAAAAAAAAAAAAAAAAA8PDwZ/Hx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx
|
||||||
|
8YLD6K/rv+eo/7/nqP+/56j/v+eo/7/nqP+/56j/v+eo/7/nqP+u5I//kt5n/4zdXf+N3l//jt5g/4/e
|
||||||
|
Yv+Q32P/luFs/67kj//D6K3/xOmu/8Tpr//F6bD/xemw/8bpsf/G6bL/xuqy/8fqtP7o7+WR8fHxgvHx
|
||||||
|
8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvPz8xYAAAAAAAAAAAAAAAAAAAAA8vLyPPHx8YLx8fGC8fHxgvHx
|
||||||
|
8YLx8fGC8fHxgvHx8YLV7ci0v+eo/7/nqP+/56j/v+eo/7/nqP+/56j/v+eo/7/nqP+/56j/wOio/7Xl
|
||||||
|
mv+u5I7/rOSM/67kj/+35pz/wumr/8Lorf/D6K3/w+it/8Tprv/E6a//xemw/8XpsP/G6bH/xumy/9Ds
|
||||||
|
wNPx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8vLyZQAAAAAAAAAAAAAAAAAAAAAAAAAA////DPHx
|
||||||
|
8YDx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGCx+m03L/nqP+/56j/v+eo/7/nqP+/56j/v+eo/7/n
|
||||||
|
qP+/56j/v+eo/7/nqP+/56j/wOep/8Doqv/B6Kr/weir/8LorP/C6K3/w+it/8Porv/E6a7/xOmv/8Xp
|
||||||
|
sP/F6bD/yOq18uvw6Yvx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC7+/vMQAAAAAAAAAAAAAAAAAA
|
||||||
|
AAAAAAAAAAAAAPHx8Vzx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC6O/ij8LorPG/56j/v+eo/7/n
|
||||||
|
qP+/56j/v+eo/7/nqP+/56j/v+eo/7/nqP+/56j/v+eo/8Dnqf/A6Kr/weiq/8Hoq//C6Kz/wuit/8Po
|
||||||
|
rf/D6K7/xOmu/8Tpr//F6bH74u/anvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLw8PB6////BQAA
|
||||||
|
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAPPz8yrx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxguHu
|
||||||
|
2pnB56v2v+eo/7/nqP+/56j/v+eo/7/nqP+/56j/v+eo/7/nqP+/56j/v+eo/7/nqP/A56n/wOiq/8Ho
|
||||||
|
q//B6Kv/wuis/8Lorf/D6K3/w+mu/8Tprv3b7dKq8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx
|
||||||
|
8YLx8fFJAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHy8vJf8fHxgvHx8YLx8fGC8fHxgvHx
|
||||||
|
8YLx8fGC8fHxgvHx8YLi7tyXwumt8L/nqP+/56j/v+eo/7/nqP+/56j/v+eo/7/nqP+/56j/v+eo/7/n
|
||||||
|
qP+/56j/wOep/8Doqv/B6Kv/weir/8LorP/C6K3/xOiv+d7u1aTx8fGC8fHxgvHx8YLx8fGC8fHxgvHx
|
||||||
|
8YLx8fGC8fHxgvLy8nb///8KAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADv7+8Q8/Pze/Hx
|
||||||
|
8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC6/Dpiszqu82/56j/v+eo/7/nqP+/56j/v+eo/7/n
|
||||||
|
qP+/56j/v+eo/7/nqP+/56j/v+eo/8Dnqf/A6Kr/weir/8Hoq//H6bTj5e7elfHx8YLx8fGC8fHxgvHx
|
||||||
|
8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvPz8yoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||||
|
AAAAAAAA9fX1MvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLe7tShx+mz3r/n
|
||||||
|
qP+/56j/v+eo/7/nqP+/56j/v+eo/7/nqP+/56j/v+eo/7/nqP/A56n/xumy5drtz6rv8e+D8fHxgvHx
|
||||||
|
8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8vLyTgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||||
|
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAPHx8Unx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx
|
||||||
|
8YLx8fGC8fHxgubv45DU68e2y+q6z8XoseTD6a7uweir9MPpru7F6bHly+q50tLsxLrl796U8fHxgvHx
|
||||||
|
8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLy8vJh////AwAAAAAAAAAAAAAAAAAA
|
||||||
|
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wHx8fFZ8fHxgvHx8YLx8fGC8fHxgvHx
|
||||||
|
8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx
|
||||||
|
8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8Wzf398IAAAAAAAA
|
||||||
|
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8D8/PzVfHx
|
||||||
|
8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx
|
||||||
|
8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8PDwZujo
|
||||||
|
6AsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||||
|
AAAAAAAA////AfHx8Ujx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx
|
||||||
|
8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx
|
||||||
|
8YLx8fFa////BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||||
|
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADz8/Mp8vLydvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx
|
||||||
|
8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx
|
||||||
|
8YLx8fGC8/PzfPHx8TcAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||||
|
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////CvLy8lDz8/N/8fHxgvHx
|
||||||
|
8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx
|
||||||
|
8YLx8fGC8fHxgvPz84Hx8fFa8PDwEQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||||
|
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||||
|
AADw8PAR8vLyTvHx8X3x8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx
|
||||||
|
8YLx8fGC8fHxgvHx8YLx8fF/8/PzVvT09BgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||||
|
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||||
|
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wXz8/Mq8/PzU/Hx8XDx8fGB8fHxgvHx8YLx8fGC8fHxgvHx
|
||||||
|
8YLx8fGC8fHxgvHx8YLy8vJz8fHxWO/v7y////8IAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||||
|
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||||
|
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8G7e3tHfLy
|
||||||
|
8ifu7u4u8PDwNPT09C/y8vIo7+/vH+Pj4wkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||||
|
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||||
|
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||||
|
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||||
|
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||||
|
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||||
|
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||||
|
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||||
|
AAAAAAAAAAAAAP///////wAA////////AAD///////8AAP//gAf//wAA//gAAD//AAD/wAAAB/8AAP+A
|
||||||
|
AAAB/wAA/gAAAAB/AAD8AAAAAD8AAPgAAAAAHwAA8AAAAAAPAADwAAAAAAcAAOAAAAAABwAA4AAAAAAD
|
||||||
|
AADAAAAAAAMAAMAAAAAAAwAAwAAAAAABAACAAAAAAAEAAIAAAAAAAQAAgAAAAAABAACAAAAAAAEAAIAA
|
||||||
|
AAAAAQAAgAAAAAABAACAAAAAAAMAAMAAAAAAAwAAwAAAAAADAADAAAAAAAMAAMAAAAAABwAAwAAAAAAH
|
||||||
|
AADgAAAAAAcAAOAAAAAADwAA4AAAAAAPAADwAAAAAB8AAPAAAAAAHwAA+AAAAAA/AAD8AAAAAD8AAPwA
|
||||||
|
AAAAfwAA/gAAAAD/AAD/AAAAAf8AAP+AAAAD/wAA/8AAAAf/AAD/8AAAH/8AAP/8AAA//wAA//8AAf//
|
||||||
|
AAD//+AP//8AAP///////wAA////////AAD///////8AACgAAAAgAAAAQAAAAAEAIAAAAAAAABAAAAAA
|
||||||
|
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||||
|
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||||
|
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||||
|
AAAAAAAAgICAAu/v7xD09PQX7u7uHvDw8CP29vYb8vLyFOrq6gwAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||||
|
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICA
|
||||||
|
gALy8vIm7+/vT/Pz82fz8/N98fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvDw8Hrw8PBm7+/vUPT0
|
||||||
|
9C3o6OgLAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOPj
|
||||||
|
4wnz8/NC8vLydPHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx
|
||||||
|
8YLx8fGC8fHxgvHx8YHy8vJj8/PzKoCAgAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||||
|
AADx8fEl8vLydfHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx
|
||||||
|
8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxcfHx8SUAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||||
|
AAAAAAAA9PT0LfHx8YDx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx
|
||||||
|
8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8/PzgPLy8j0AAAABAAAAAAAA
|
||||||
|
AAAAAAAAAAAAAO3t7Rzx8fGA8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLr8OmM5O7emeTv
|
||||||
|
3Z7h79mj5fDem+nv45Tu8u6H8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvLy
|
||||||
|
8joAAAAAAAAAAAAAAAD///8E8fHxbvHx8YLx8fGC8fHxgvHx8YLx8fGC7vDshtns0K7N67zayeq288fq
|
||||||
|
s//I6rT/yOq1/8nqtv/K67f/y+u4/8vruf/P7L7w0+zF29vv0Lrn8OKX8fHxgvHx8YLx8fGC8fHxgvHx
|
||||||
|
8YLx8fGC8/PzfvPz8xUAAAAAAAAAAPX19TLx8fGC8fHxgvHx8YLx8fGC8fHxgt3u1KXF6rHzxOmv/8Xp
|
||||||
|
sP/G6bH/xumy/8fqs//I6rT/yOq1/8nqtv/K67f/y+u4/8vruf/M67v/zey8/87svf/S7MPj4u7Zp/Hx
|
||||||
|
8YLx8fGC8fHxgvHx8YLx8fGC8/PzVQAAAAAAAAAA8fHxavHx8YLx8fGC8fHxgvHx8YLf7defwuis/cPo
|
||||||
|
rf/E6a7/xOmv/8XpsP/G6bH/xumy/8fqs//I6rT/yOq1/8nqtv/K67f/y+u4/8vruv/M67v/zey8/87s
|
||||||
|
vf/N67z/3e7SufHx8YLx8fGC8fHxgvHx8YLz8/N8////Bf///w3x8fGC8fHxgvHx8YLx8fGC8fHxgsXp
|
||||||
|
sOnB6Kv/wuis/8Porf/E6a7/xOmv/8XpsP/G6bH/xumy/8fqs//I6rT/yOq1/8nqtv/K67f/y+u4/8vr
|
||||||
|
uv/M67v/zey8/87svf/O67z96/Hoj/Hx8YLx8fGC8fHxgvHx8YLy8vIm8/PzK/Hx8YLx8fGC8fHxgvHx
|
||||||
|
8YLg79icwOep/8Hoqv/B6Kv/wuis/8Porf/E6a7/wuit/73opP+76KL/u+eh/77opv/D6a3/yeu1/8nq
|
||||||
|
tv/K67f/y+u5/8zruv/M67v/zey8/87svf/d7tSz8fHxgvHx8YLx8fGC8fHxgvHx8Tby8vI68fHxgvHx
|
||||||
|
8YLx8fGC8fHxgtTrxre/56j/wOep/8Hoqv/B6Kv/uOad/53idv+V4Gn/leBq/5fhbP+Y4W//muFx/5vi
|
||||||
|
c/+e4Xb/puWD/7PmlP/D6a3/y+u5/8zruv/M67v/zey8/9rtzsHx8fGC8fHxgvHx8YLx8fGC8/PzQfPz
|
||||||
|
80Lx8fGC8fHxgvHx8YLx8fGC0OvAwr/nqP+/56j/wOep/8Hoqv+o44b/kd9k/5LfZv+U4Gj/leBq/5fh
|
||||||
|
bf+Y4W//muFx/5vic/+d4nX/n+N3/7fnm//K67j/y+u5/8zruv/M67v/2u3QvPHx8YLx8fGC8fHxgvHx
|
||||||
|
8YLy8vI98/PzP/Hx8YLx8fGC8fHxgvHx8YLQ6sK/v+eo/7/nqP+/56j/wOep/6jjhv+P3mL/kd9k/5Lf
|
||||||
|
Zv+U4Gj/leBr/5fhbf+Y4W//muFx/5zic/+d4nX/v+mm/8nqt//K67j/y+u5/8zruv/f79au8fHxgvHx
|
||||||
|
8YLx8fGC8fHxgvX19TLx8fE38fHxgvHx8YLx8fGC8fHxgtTrybO/56j/v+eo/7/nqP+/56j/sOSS/47e
|
||||||
|
YP+P3mL/kd9k/5LfZv+U4Gj/leBr/5fhbf+Z4W//muJx/5/jd//H6bP/yeq2/8nqt//K67j/y+u5/+nv
|
||||||
|
45Tx8fGC8fHxgvHx8YLx8fGC7+/vIPHx8SXx8fGC8fHxgvHx8YLx8fGC4e/Zm7/nqP+/56j/v+eo/7/n
|
||||||
|
qP+956X/jt5h/47eYP+P3mL/kd9k/5LfZv+U4Gn/luBr/5fhbf+Z4W//q+aK/8fqs//I6rT/yeq2/8nq
|
||||||
|
t//N7Lvw8fHxgvHx8YLx8fGC8fHxgvPz84D///8G6+vrDfHx8YLx8fGC8fHxgvHx8YLv8e+Dweis87/n
|
||||||
|
qP+/56j/v+eo/7/nqP+d4XX/jN1e/47eYP+P3mL/kd9k/5PfZ/+U4Gn/luBr/5fhbf+86KP/xuqy/8fq
|
||||||
|
s//I6rX/yeq2/9Tsx8nx8fGC8fHxgvHx8YLx8fGC8PDwaAAAAAAAAAAA8fHxbPHx8YLx8fGC8fHxgvHx
|
||||||
|
8YLM6rrMv+eo/7/nqP+/56j/v+eo/7blmv+N3V//jN1e/47eYP+Q3mL/kd9k/5PfZ/+U4Gn/qeSH/8Xp
|
||||||
|
sP/G6bH/xuqy/8fqs//I6rX/5fDem/Hx8YLx8fGC8fHxgvHx8YLz8/M/AAAAAAAAAADz8/NB8fHxgvHx
|
||||||
|
8YLx8fGC8fHxgt3s06O/56j/v+eo/7/nqP+/56j/v+eo/7Xmmf+U32n/jN1e/47eYP+Q3mL/k99o/6zk
|
||||||
|
i//D6a7/xemv/8XpsP/G6bH/xuqy/8vqu+jx8fGC8fHxgvHx8YLx8fGC8fHxgvPz8xUAAAAAAAAAAPT0
|
||||||
|
9Bfx8fGC8fHxgvHx8YLx8fGC8fHvg8Tpsee/56j/v+eo/7/nqP+/56j/v+eo/7/nqP+35pz/suWV/7Xm
|
||||||
|
mf/A6Kj/wuit/8Porf/E6a7/xemv/8XpsP/G6bH/3e3UqvHx8YLx8fGC8fHxgvHx8YLw8PBmAAAAAAAA
|
||||||
|
AAAAAAAAAAAAAPHx8W7x8fGC8fHxgvHx8YLx8fGC4u7cmMHnqvm/56j/v+eo/7/nqP+/56j/v+eo/7/n
|
||||||
|
qP+/56j/wOep/8Hoqv/C6Kz/wuit/8Porf/E6a7/xemv/9Hrwszx8fGC8fHxgvHx8YLx8fGC8fHxgvX1
|
||||||
|
9TEAAAAAAAAAAAAAAAAAAAAA7u7uO/Hx8YLx8fGC8fHxgvHx8YLx8fGC3e7SpMHoqfq/56j/v+eo/7/n
|
||||||
|
qP+/56j/v+eo/7/nqP+/56j/wOip/8Hoq//C6Kz/wuit/8Porf/O67zV8PHwhPHx8YLx8fGC8fHxgvHx
|
||||||
|
8YLy8vJ2////BQAAAAAAAAAAAAAAAAAAAACqqqoD8PDwafHx8YLx8fGC8fHxgvHx8YLx8fGC4O/YnMTo
|
||||||
|
ruy/56j/v+eo/7/nqP+/56j/v+eo/7/nqP+/56j/wOip/8Hoq//C6Kz90uvEwe/x74Px8fGC8fHxgvHx
|
||||||
|
8YLx8fGC8fHxgvPz8ykAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADz8/MW8fHxfPHx8YLx8fGC8fHxgvHx
|
||||||
|
8YLx8fGC8PLuhdXtyLXF6bHlv+eo/7/nqP+/56j/v+eo/7/nqP/B6Kv0zeq8zOXv4JTx8fGC8fHxgvHx
|
||||||
|
8YLx8fGC8fHxgvHx8YLy8vJNAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADy8vIm8fHxgPHx
|
||||||
|
8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLs8OmJ4e/Zm93u06Pf7def5+/hkvHx8YLx8fGC8fHxgvHx
|
||||||
|
8YLx8fGC8fHxgvHx8YLx8fGC8fHxXf///wIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||||
|
AADy8vIo8/PzffHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx
|
||||||
|
8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8VnMzMwFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||||
|
AAAAAAAAAAAAAAAAAAD29vYb8fHxbvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx
|
||||||
|
8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvPz83/v7+9BgICAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||||
|
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMzMwF8/PzQPLy8nnx8fGC8fHxgvHx8YLx8fGC8fHxgvHx
|
||||||
|
8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvPz84Hx8fFc9PT0GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||||
|
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////B/X19TLx8fFc8PDwevHx
|
||||||
|
8YLx8fGC8fHxgvHx8YLx8fGC8fHxgPHx8Wv09PRE9PT0FwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||||
|
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||||
|
AAAAAAAA7+/vEPb29hvw8PAj7+/vH/T09Be/v78EAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||||
|
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||||
|
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||||
|
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////////////8B///wAA//wAAD/wAAAP4AAAB+AA
|
||||||
|
AAfAAAADwAAAA4AAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAADwAAAA8AAAAPAAAAH4AAAB+AA
|
||||||
|
AA/wAAAP+AAAH/gAAD/+AAB//wAB///AA///+B////////////8oAAAAEAAAACAAAAABACAAAAAAAAAE
|
||||||
|
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////CfDw8BH///8GAAAAAAAA
|
||||||
|
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgICAAu7u7i7x8fFe8PDwevHx8YLx8fGC8fHxgvDw
|
||||||
|
8Hvx8fFs7+/vT/Dw8CMAAAABAAAAAAAAAAAAAAAA5ubmCvLy8l/x8fGC8fHxgvHx8YLx8fGC8fHxgvHx
|
||||||
|
8YLx8fGC8fHxgvHx8YLx8fGC8/PzZu7u7g8AAAAAAAAAAPHx8V3x8fGC8fHxgunv5o7Z7c200+vFytTs
|
||||||
|
xc7W7cnH2+7QueLu2qbu8OyH8fHxgvHx8YLx8fFu////BfHx8STx8fGC8fHxgtrtzq3D6a/8xemw/8bp
|
||||||
|
sv/I6rT/yeq2/8vruP/M67v/z+u++Nzu0bjx8fGC8fHxgu/v7zDx8fFI8fHxguzw6ojC56z3wuis/8Tp
|
||||||
|
rv/E6q3/weiq/8fqsv/J6rb/y+u5/8zru//N67z/6/HpjfHx8YLy8vJN8fHxXPHx8YLg79icv+eo/8Ho
|
||||||
|
qv+k4n//lOBo/5fhbf+a4XH/n+J5/7Pmlv/L67n/zOu7/+Xw353x8fGC8fHxXvHx8Vrx8fGC4O3Zm7/n
|
||||||
|
qP+/56j/nuF3/5HfZP+U4Gj/l+Ft/5ricf+x5pL/yeq3/8vruf/r8emN8fHxgu/v70/x8fFK8fHxguzw
|
||||||
|
6ojA6Kn8v+eo/6njiP+O3mD/kd9k/5Tgaf+X4W3/vuim/8jqtP/N67zr8fHxgvHx8YLy8vI68/PzK/Hx
|
||||||
|
8YLx8fGCx+m03L/nqP++6Kb/meBw/47eYP+S32X/q+SL/8XpsP/G6rL/1+zLvvHx8YLz8/OB8PDwEdXV
|
||||||
|
1Qbx8fF98fHxgt/t1Z/A56j9v+eo/7/nqP+656H/vuim/8Lorf/E6a7/yOq18Ovw6Yvx8fGC8vLyYwAA
|
||||||
|
AAAAAAAA8fHxR/Hx8YLx8fGC2O3NrMDnqfq/56j/v+eo/7/nqP/B6Kv/xumy7OTu3Zfx8fGC8/PzgfLy
|
||||||
|
8icAAAAAAAAAAP///wPz8/Nm8fHxgvHx8YLo7+SO0+zFuczquszM6bzJ1+zMru7w7Ibx8fGC8fHxgvHx
|
||||||
|
8UcAAAAAAAAAAAAAAAAAAAAA4+PjCfHx8Vzx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgfPz
|
||||||
|
80D///8BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB8/PzK/Ly8mDz8/N+8fHxgvHx8YLy8vJ68vLyUezs
|
||||||
|
7BsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAevr6w3j4+MJAAAAAAAA
|
||||||
|
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAP//AAD8fwAA4AcAAMADAACAAQAAgAEAAIABAACAAQAAgAEAAIAB
|
||||||
|
AADAAwAAwAMAAOAHAADwDwAA/n8AAP//AAA=
|
||||||
|
</value>
|
||||||
|
</data>
|
||||||
|
</root>
|
3
extra/exe-builder/FodyWeavers.xml
Normal file
3
extra/exe-builder/FodyWeavers.xml
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
|
||||||
|
<Costura />
|
||||||
|
</Weavers>
|
141
extra/exe-builder/FodyWeavers.xsd
Normal file
141
extra/exe-builder/FodyWeavers.xsd
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
|
||||||
|
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. -->
|
||||||
|
<xs:element name="Weavers">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:all>
|
||||||
|
<xs:element name="Costura" minOccurs="0" maxOccurs="1">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:all>
|
||||||
|
<xs:element minOccurs="0" maxOccurs="1" name="ExcludeAssemblies" type="xs:string">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>A list of assembly names to exclude from the default action of "embed all Copy Local references", delimited with line breaks</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element minOccurs="0" maxOccurs="1" name="IncludeAssemblies" type="xs:string">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>A list of assembly names to include from the default action of "embed all Copy Local references", delimited with line breaks.</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element minOccurs="0" maxOccurs="1" name="ExcludeRuntimeAssemblies" type="xs:string">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>A list of runtime assembly names to exclude from the default action of "embed all Copy Local references", delimited with line breaks</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element minOccurs="0" maxOccurs="1" name="IncludeRuntimeAssemblies" type="xs:string">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>A list of runtime assembly names to include from the default action of "embed all Copy Local references", delimited with line breaks.</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element minOccurs="0" maxOccurs="1" name="Unmanaged32Assemblies" type="xs:string">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>A list of unmanaged 32 bit assembly names to include, delimited with line breaks.</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element minOccurs="0" maxOccurs="1" name="Unmanaged64Assemblies" type="xs:string">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>A list of unmanaged 64 bit assembly names to include, delimited with line breaks.</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element minOccurs="0" maxOccurs="1" name="PreloadOrder" type="xs:string">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>The order of preloaded assemblies, delimited with line breaks.</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:element>
|
||||||
|
</xs:all>
|
||||||
|
<xs:attribute name="CreateTemporaryAssemblies" type="xs:boolean">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>This will copy embedded files to disk before loading them into memory. This is helpful for some scenarios that expected an assembly to be loaded from a physical file.</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:attribute>
|
||||||
|
<xs:attribute name="IncludeDebugSymbols" type="xs:boolean">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Controls if .pdbs for reference assemblies are also embedded.</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:attribute>
|
||||||
|
<xs:attribute name="IncludeRuntimeReferences" type="xs:boolean">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Controls if runtime assemblies are also embedded.</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:attribute>
|
||||||
|
<xs:attribute name="UseRuntimeReferencePaths" type="xs:boolean">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Controls whether the runtime assemblies are embedded with their full path or only with their assembly name.</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:attribute>
|
||||||
|
<xs:attribute name="DisableCompression" type="xs:boolean">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Embedded assemblies are compressed by default, and uncompressed when they are loaded. You can turn compression off with this option.</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:attribute>
|
||||||
|
<xs:attribute name="DisableCleanup" type="xs:boolean">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>As part of Costura, embedded assemblies are no longer included as part of the build. This cleanup can be turned off.</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:attribute>
|
||||||
|
<xs:attribute name="LoadAtModuleInit" type="xs:boolean">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Costura by default will load as part of the module initialization. This flag disables that behavior. Make sure you call CosturaUtility.Initialize() somewhere in your code.</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:attribute>
|
||||||
|
<xs:attribute name="IgnoreSatelliteAssemblies" type="xs:boolean">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Costura will by default use assemblies with a name like 'resources.dll' as a satellite resource and prepend the output path. This flag disables that behavior.</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:attribute>
|
||||||
|
<xs:attribute name="ExcludeAssemblies" type="xs:string">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>A list of assembly names to exclude from the default action of "embed all Copy Local references", delimited with |</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:attribute>
|
||||||
|
<xs:attribute name="IncludeAssemblies" type="xs:string">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>A list of assembly names to include from the default action of "embed all Copy Local references", delimited with |.</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:attribute>
|
||||||
|
<xs:attribute name="ExcludeRuntimeAssemblies" type="xs:string">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>A list of runtime assembly names to exclude from the default action of "embed all Copy Local references", delimited with |</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:attribute>
|
||||||
|
<xs:attribute name="IncludeRuntimeAssemblies" type="xs:string">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>A list of runtime assembly names to include from the default action of "embed all Copy Local references", delimited with |.</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:attribute>
|
||||||
|
<xs:attribute name="Unmanaged32Assemblies" type="xs:string">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>A list of unmanaged 32 bit assembly names to include, delimited with |.</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:attribute>
|
||||||
|
<xs:attribute name="Unmanaged64Assemblies" type="xs:string">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>A list of unmanaged 64 bit assembly names to include, delimited with |.</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:attribute>
|
||||||
|
<xs:attribute name="PreloadOrder" type="xs:string">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>The order of preloaded assemblies, delimited with |.</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:attribute>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
</xs:all>
|
||||||
|
<xs:attribute name="VerifyAssembly" type="xs:boolean">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:attribute>
|
||||||
|
<xs:attribute name="VerifyIgnoreCodes" type="xs:string">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:attribute>
|
||||||
|
<xs:attribute name="GenerateXsd" type="xs:boolean">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:attribute>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
</xs:schema>
|
243
extra/exe-builder/Program.cs
Normal file
243
extra/exe-builder/Program.cs
Normal file
@@ -0,0 +1,243 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Drawing;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.Sockets;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
using Microsoft.Win32;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using UptimeKuma.Properties;
|
||||||
|
|
||||||
|
namespace UptimeKuma {
|
||||||
|
static class Program {
|
||||||
|
/// <summary>
|
||||||
|
/// The main entry point for the application.
|
||||||
|
/// </summary>
|
||||||
|
[STAThread]
|
||||||
|
static void Main(string[] args) {
|
||||||
|
var cwd = Path.GetDirectoryName(Application.ExecutablePath);
|
||||||
|
|
||||||
|
if (cwd != null) {
|
||||||
|
Environment.CurrentDirectory = cwd;
|
||||||
|
}
|
||||||
|
|
||||||
|
Application.EnableVisualStyles();
|
||||||
|
Application.SetCompatibleTextRenderingDefault(false);
|
||||||
|
Application.Run(new UptimeKumaApplicationContext());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class UptimeKumaApplicationContext : ApplicationContext
|
||||||
|
{
|
||||||
|
private static Mutex mutex = null;
|
||||||
|
|
||||||
|
const string appName = "Uptime Kuma";
|
||||||
|
|
||||||
|
private NotifyIcon trayIcon;
|
||||||
|
private Process process;
|
||||||
|
|
||||||
|
private MenuItem statusMenuItem;
|
||||||
|
private MenuItem runWhenStarts;
|
||||||
|
private MenuItem openMenuItem;
|
||||||
|
|
||||||
|
private RegistryKey registryKey = Registry.CurrentUser.OpenSubKey("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run", true);
|
||||||
|
|
||||||
|
|
||||||
|
public UptimeKumaApplicationContext() {
|
||||||
|
|
||||||
|
// Single instance only
|
||||||
|
bool createdNew;
|
||||||
|
mutex = new Mutex(true, appName, out createdNew);
|
||||||
|
if (!createdNew) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var startingText = "Starting server...";
|
||||||
|
trayIcon = new NotifyIcon();
|
||||||
|
trayIcon.Text = startingText;
|
||||||
|
|
||||||
|
runWhenStarts = new MenuItem("Run when system starts", RunWhenStarts);
|
||||||
|
runWhenStarts.Checked = registryKey.GetValue(appName) != null;
|
||||||
|
|
||||||
|
statusMenuItem = new MenuItem(startingText);
|
||||||
|
statusMenuItem.Enabled = false;
|
||||||
|
|
||||||
|
openMenuItem = new MenuItem("Open", Open);
|
||||||
|
openMenuItem.Enabled = false;
|
||||||
|
|
||||||
|
trayIcon.Icon = Icon.ExtractAssociatedIcon(Assembly.GetExecutingAssembly().Location);
|
||||||
|
trayIcon.ContextMenu = new ContextMenu(new MenuItem[] {
|
||||||
|
statusMenuItem,
|
||||||
|
openMenuItem,
|
||||||
|
//new("Debug Console", DebugConsole),
|
||||||
|
runWhenStarts,
|
||||||
|
new("Check for Update...", CheckForUpdate),
|
||||||
|
new("Visit GitHub...", VisitGitHub),
|
||||||
|
new("About", About),
|
||||||
|
new("Exit", Exit),
|
||||||
|
});
|
||||||
|
|
||||||
|
trayIcon.MouseDoubleClick += new MouseEventHandler(Open);
|
||||||
|
trayIcon.Visible = true;
|
||||||
|
|
||||||
|
var hasUpdateFile = File.Exists("update");
|
||||||
|
|
||||||
|
if (!hasUpdateFile && Directory.Exists("core") && Directory.Exists("node") && Directory.Exists("core/node_modules") && Directory.Exists("core/dist")) {
|
||||||
|
// Go go go
|
||||||
|
StartProcess();
|
||||||
|
} else {
|
||||||
|
DownloadFiles();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DownloadFiles() {
|
||||||
|
var form = new DownloadForm();
|
||||||
|
form.Closed += Exit;
|
||||||
|
form.Show();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RunWhenStarts(object sender, EventArgs e) {
|
||||||
|
if (registryKey == null) {
|
||||||
|
MessageBox.Show("Error: Unable to set startup registry key.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (runWhenStarts.Checked) {
|
||||||
|
registryKey.DeleteValue(appName, false);
|
||||||
|
runWhenStarts.Checked = false;
|
||||||
|
} else {
|
||||||
|
registryKey.SetValue(appName, Application.ExecutablePath);
|
||||||
|
runWhenStarts.Checked = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void StartProcess() {
|
||||||
|
var startInfo = new ProcessStartInfo {
|
||||||
|
FileName = "node/node.exe",
|
||||||
|
Arguments = "server/server.js --data-dir=\"../data/\"",
|
||||||
|
RedirectStandardOutput = false,
|
||||||
|
RedirectStandardError = false,
|
||||||
|
UseShellExecute = false,
|
||||||
|
CreateNoWindow = true,
|
||||||
|
WorkingDirectory = "core"
|
||||||
|
};
|
||||||
|
|
||||||
|
process = new Process();
|
||||||
|
process.StartInfo = startInfo;
|
||||||
|
process.EnableRaisingEvents = true;
|
||||||
|
process.Exited += ProcessExited;
|
||||||
|
|
||||||
|
try {
|
||||||
|
process.Start();
|
||||||
|
//Open(null, null);
|
||||||
|
|
||||||
|
// Async task to check if the server is ready
|
||||||
|
Task.Run(() => {
|
||||||
|
var runningText = "Server is running";
|
||||||
|
using TcpClient tcpClient = new TcpClient();
|
||||||
|
while (true) {
|
||||||
|
try {
|
||||||
|
tcpClient.Connect("127.0.0.1", 3001);
|
||||||
|
statusMenuItem.Text = runningText;
|
||||||
|
openMenuItem.Enabled = true;
|
||||||
|
trayIcon.Text = runningText;
|
||||||
|
break;
|
||||||
|
} catch (Exception) {
|
||||||
|
System.Threading.Thread.Sleep(2000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
MessageBox.Show("Startup failed: " + e.Message, "Uptime Kuma Error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void StopProcess() {
|
||||||
|
process?.Kill();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Open(object sender, EventArgs e) {
|
||||||
|
Process.Start("http://localhost:3001");
|
||||||
|
}
|
||||||
|
|
||||||
|
void DebugConsole(object sender, EventArgs e) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void CheckForUpdate(object sender, EventArgs e) {
|
||||||
|
var needUpdate = false;
|
||||||
|
|
||||||
|
// Check version.json exists
|
||||||
|
if (File.Exists("version.json")) {
|
||||||
|
// Load version.json and compare with the latest version from GitHub
|
||||||
|
var currentVersionObj = JsonConvert.DeserializeObject<Version>(File.ReadAllText("version.json"));
|
||||||
|
|
||||||
|
var versionJson = new WebClient().DownloadString("https://uptime.kuma.pet/version");
|
||||||
|
var latestVersionObj = JsonConvert.DeserializeObject<Version>(versionJson);
|
||||||
|
|
||||||
|
// Compare version, if the latest version is newer, then update
|
||||||
|
if (new System.Version(latestVersionObj.latest).CompareTo(new System.Version(currentVersionObj.latest)) > 0) {
|
||||||
|
var result = MessageBox.Show("A new version is available. Do you want to update?", "Update", MessageBoxButtons.YesNo);
|
||||||
|
if (result == DialogResult.Yes) {
|
||||||
|
// Create a empty file `update`, so the app will download the core files again at startup
|
||||||
|
File.Create("update").Close();
|
||||||
|
|
||||||
|
trayIcon.Visible = false;
|
||||||
|
process?.Kill();
|
||||||
|
|
||||||
|
// Restart the app, it will download the core files again at startup
|
||||||
|
Application.Restart();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
MessageBox.Show("You are using the latest version.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void VisitGitHub(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
Process.Start("https://github.com/louislam/uptime-kuma");
|
||||||
|
}
|
||||||
|
|
||||||
|
void About(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
MessageBox.Show("Uptime Kuma Windows Runtime v1.0.0" + Environment.NewLine + "© 2023 Louis Lam", "Info");
|
||||||
|
}
|
||||||
|
|
||||||
|
void Exit(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
// Hide tray icon, otherwise it will remain shown until user mouses over it
|
||||||
|
trayIcon.Visible = false;
|
||||||
|
process?.Kill();
|
||||||
|
Application.Exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProcessExited(object sender, EventArgs e) {
|
||||||
|
|
||||||
|
if (process.ExitCode != 0) {
|
||||||
|
var line = "";
|
||||||
|
while (!process.StandardOutput.EndOfStream)
|
||||||
|
{
|
||||||
|
line += process.StandardOutput.ReadLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
MessageBox.Show("Uptime Kuma exited unexpectedly. Exit code: " + process.ExitCode + " " + line);
|
||||||
|
}
|
||||||
|
|
||||||
|
trayIcon.Visible = false;
|
||||||
|
Application.Exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
36
extra/exe-builder/Properties/AssemblyInfo.cs
Normal file
36
extra/exe-builder/Properties/AssemblyInfo.cs
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
using System.Reflection;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
// General Information about an assembly is controlled through the following
|
||||||
|
// set of attributes. Change these attribute values to modify the information
|
||||||
|
// associated with an assembly.
|
||||||
|
[assembly: AssemblyTitle("Uptime Kuma")]
|
||||||
|
[assembly: AssemblyDescription("")]
|
||||||
|
[assembly: AssemblyConfiguration("")]
|
||||||
|
[assembly: AssemblyCompany("")]
|
||||||
|
[assembly: AssemblyProduct("Uptime Kuma")]
|
||||||
|
[assembly: AssemblyCopyright("Copyright © 2023 Louis Lam")]
|
||||||
|
[assembly: AssemblyTrademark("")]
|
||||||
|
[assembly: AssemblyCulture("")]
|
||||||
|
|
||||||
|
// Setting ComVisible to false makes the types in this assembly not visible
|
||||||
|
// to COM components. If you need to access a type in this assembly from
|
||||||
|
// COM, set the ComVisible attribute to true on that type.
|
||||||
|
[assembly: ComVisible(false)]
|
||||||
|
|
||||||
|
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||||
|
[assembly: Guid("2DB53988-1D93-4AC0-90C4-96ADEAAC5C04")]
|
||||||
|
|
||||||
|
// Version information for an assembly consists of the following four values:
|
||||||
|
//
|
||||||
|
// Major Version
|
||||||
|
// Minor Version
|
||||||
|
// Build Number
|
||||||
|
// Revision
|
||||||
|
//
|
||||||
|
// You can specify all the values or you can default the Build and Revision Numbers
|
||||||
|
// by using the '*' as shown below:
|
||||||
|
// [assembly: AssemblyVersion("1.0.*")]
|
||||||
|
[assembly: AssemblyVersion("1.0.0.0")]
|
||||||
|
[assembly: AssemblyFileVersion("1.0.0.0")]
|
62
extra/exe-builder/Properties/Resources.Designer.cs
generated
Normal file
62
extra/exe-builder/Properties/Resources.Designer.cs
generated
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// <auto-generated>
|
||||||
|
// This code was generated by a tool.
|
||||||
|
// Runtime Version:4.0.30319.42000
|
||||||
|
//
|
||||||
|
// Changes to this file may cause incorrect behavior and will be lost if
|
||||||
|
// the code is regenerated.
|
||||||
|
// </auto-generated>
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
namespace UptimeKuma.Properties {
|
||||||
|
/// <summary>
|
||||||
|
/// A strongly-typed resource class, for looking up localized strings, etc.
|
||||||
|
/// </summary>
|
||||||
|
// This class was auto-generated by the StronglyTypedResourceBuilder
|
||||||
|
// class via a tool like ResGen or Visual Studio.
|
||||||
|
// To add or remove a member, edit your .ResX file then rerun ResGen
|
||||||
|
// with the /str option, or rebuild your VS project.
|
||||||
|
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder",
|
||||||
|
"4.0.0.0")]
|
||||||
|
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||||
|
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||||
|
internal class Resources {
|
||||||
|
private static global::System.Resources.ResourceManager resourceMan;
|
||||||
|
|
||||||
|
private static global::System.Globalization.CultureInfo resourceCulture;
|
||||||
|
|
||||||
|
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance",
|
||||||
|
"CA1811:AvoidUncalledPrivateCode")]
|
||||||
|
internal Resources() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the cached ResourceManager instance used by this class.
|
||||||
|
/// </summary>
|
||||||
|
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState
|
||||||
|
.Advanced)]
|
||||||
|
internal static global::System.Resources.ResourceManager ResourceManager {
|
||||||
|
get {
|
||||||
|
if ((resourceMan == null)) {
|
||||||
|
global::System.Resources.ResourceManager temp =
|
||||||
|
new global::System.Resources.ResourceManager("UptimeKuma.Properties.Resources",
|
||||||
|
typeof(Resources).Assembly);
|
||||||
|
resourceMan = temp;
|
||||||
|
}
|
||||||
|
|
||||||
|
return resourceMan;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Overrides the current thread's CurrentUICulture property for all
|
||||||
|
/// resource lookups using this strongly typed resource class.
|
||||||
|
/// </summary>
|
||||||
|
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState
|
||||||
|
.Advanced)]
|
||||||
|
internal static global::System.Globalization.CultureInfo Culture {
|
||||||
|
get { return resourceCulture; }
|
||||||
|
set { resourceCulture = value; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
117
extra/exe-builder/Properties/Resources.resx
Normal file
117
extra/exe-builder/Properties/Resources.resx
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<root>
|
||||||
|
<!--
|
||||||
|
Microsoft ResX Schema
|
||||||
|
|
||||||
|
Version 2.0
|
||||||
|
|
||||||
|
The primary goals of this format is to allow a simple XML format
|
||||||
|
that is mostly human readable. The generation and parsing of the
|
||||||
|
various data types are done through the TypeConverter classes
|
||||||
|
associated with the data types.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
... ado.net/XML headers & schema ...
|
||||||
|
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||||
|
<resheader name="version">2.0</resheader>
|
||||||
|
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||||
|
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||||
|
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||||
|
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||||
|
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||||
|
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||||
|
</data>
|
||||||
|
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||||
|
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||||
|
<comment>This is a comment</comment>
|
||||||
|
</data>
|
||||||
|
|
||||||
|
There are any number of "resheader" rows that contain simple
|
||||||
|
name/value pairs.
|
||||||
|
|
||||||
|
Each data row contains a name, and value. The row also contains a
|
||||||
|
type or mimetype. Type corresponds to a .NET class that support
|
||||||
|
text/value conversion through the TypeConverter architecture.
|
||||||
|
Classes that don't support this are serialized and stored with the
|
||||||
|
mimetype set.
|
||||||
|
|
||||||
|
The mimetype is used for serialized objects, and tells the
|
||||||
|
ResXResourceReader how to depersist the object. This is currently not
|
||||||
|
extensible. For a given mimetype the value must be set accordingly:
|
||||||
|
|
||||||
|
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||||
|
that the ResXResourceWriter will generate, however the reader can
|
||||||
|
read any of the formats listed below.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.binary.base64
|
||||||
|
value : The object must be serialized with
|
||||||
|
: System.Serialization.Formatters.Binary.BinaryFormatter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.soap.base64
|
||||||
|
value : The object must be serialized with
|
||||||
|
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||||
|
value : The object must be serialized into a byte array
|
||||||
|
: using a System.ComponentModel.TypeConverter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
-->
|
||||||
|
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||||
|
<xsd:element name="root" msdata:IsDataSet="true">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:choice maxOccurs="unbounded">
|
||||||
|
<xsd:element name="metadata">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" type="xsd:string" />
|
||||||
|
<xsd:attribute name="type" type="xsd:string" />
|
||||||
|
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="assembly">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:attribute name="alias" type="xsd:string" />
|
||||||
|
<xsd:attribute name="name" type="xsd:string" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="data">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||||
|
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
|
||||||
|
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||||
|
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="resheader">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
</xsd:choice>
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
</xsd:schema>
|
||||||
|
<resheader name="resmimetype">
|
||||||
|
<value>text/microsoft-resx</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="version">
|
||||||
|
<value>2.0</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="reader">
|
||||||
|
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="writer">
|
||||||
|
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
|
</resheader>
|
||||||
|
</root>
|
23
extra/exe-builder/Properties/Settings.Designer.cs
generated
Normal file
23
extra/exe-builder/Properties/Settings.Designer.cs
generated
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// <auto-generated>
|
||||||
|
// This code was generated by a tool.
|
||||||
|
// Runtime Version:4.0.30319.42000
|
||||||
|
//
|
||||||
|
// Changes to this file may cause incorrect behavior and will be lost if
|
||||||
|
// the code is regenerated.
|
||||||
|
// </auto-generated>
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
namespace UptimeKuma.Properties {
|
||||||
|
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||||
|
[global::System.CodeDom.Compiler.GeneratedCodeAttribute(
|
||||||
|
"Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")]
|
||||||
|
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
|
||||||
|
private static Settings defaultInstance =
|
||||||
|
((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
|
||||||
|
|
||||||
|
public static Settings Default {
|
||||||
|
get { return defaultInstance; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
7
extra/exe-builder/Properties/Settings.settings
Normal file
7
extra/exe-builder/Properties/Settings.settings
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<?xml version='1.0' encoding='utf-8'?>
|
||||||
|
<SettingsFile xmlns="http://schemas.microsoft.com/VisualStudio/2004/01/settings" CurrentProfile="(Default)">
|
||||||
|
<Profiles>
|
||||||
|
<Profile Name="(Default)" />
|
||||||
|
</Profiles>
|
||||||
|
<Settings />
|
||||||
|
</SettingsFile>
|
212
extra/exe-builder/UptimeKuma.csproj
Normal file
212
extra/exe-builder/UptimeKuma.csproj
Normal file
@@ -0,0 +1,212 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
<Import Project="packages\Costura.Fody.5.7.0\build\Costura.Fody.props" Condition="Exists('packages\Costura.Fody.5.7.0\build\Costura.Fody.props')" />
|
||||||
|
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||||
|
<PropertyGroup>
|
||||||
|
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||||
|
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||||
|
<ProjectGuid>{2DB53988-1D93-4AC0-90C4-96ADEAAC5C04}</ProjectGuid>
|
||||||
|
<OutputType>WinExe</OutputType>
|
||||||
|
<RootNamespace>UptimeKuma</RootNamespace>
|
||||||
|
<AssemblyName>uptime-kuma</AssemblyName>
|
||||||
|
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
|
||||||
|
<FileAlignment>512</FileAlignment>
|
||||||
|
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
||||||
|
<Deterministic>true</Deterministic>
|
||||||
|
<ApplicationIcon>..\..\public\favicon.ico</ApplicationIcon>
|
||||||
|
<LangVersion>9</LangVersion>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||||
|
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||||
|
<DebugSymbols>true</DebugSymbols>
|
||||||
|
<DebugType>full</DebugType>
|
||||||
|
<Optimize>false</Optimize>
|
||||||
|
<OutputPath>bin\Debug\</OutputPath>
|
||||||
|
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||||
|
<ErrorReport>prompt</ErrorReport>
|
||||||
|
<WarningLevel>4</WarningLevel>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||||
|
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||||
|
<DebugType>pdbonly</DebugType>
|
||||||
|
<Optimize>true</Optimize>
|
||||||
|
<OutputPath>bin\Release\</OutputPath>
|
||||||
|
<DefineConstants>TRACE</DefineConstants>
|
||||||
|
<ErrorReport>prompt</ErrorReport>
|
||||||
|
<WarningLevel>4</WarningLevel>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup>
|
||||||
|
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup>
|
||||||
|
<PostBuildEvent>COPY "$(SolutionDir)bin\Debug\uptime-kuma.exe" "%UserProfile%\Desktop\uptime-kuma-win64\"</PostBuildEvent>
|
||||||
|
</PropertyGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Reference Include="Costura, Version=5.7.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||||
|
<HintPath>packages\Costura.Fody.5.7.0\lib\netstandard1.0\Costura.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="Microsoft.Win32.Primitives, Version=4.0.2.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||||
|
<HintPath>packages\Microsoft.Win32.Primitives.4.3.0\lib\net46\Microsoft.Win32.Primitives.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="mscorlib" />
|
||||||
|
<Reference Include="Newtonsoft.Json, Version=13.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
|
||||||
|
<HintPath>packages\Newtonsoft.Json.13.0.2\lib\net45\Newtonsoft.Json.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="System" />
|
||||||
|
<Reference Include="System.AppContext, Version=4.1.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||||
|
<HintPath>packages\System.AppContext.4.3.0\lib\net463\System.AppContext.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="System.Buffers, Version=4.0.3.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
|
||||||
|
<HintPath>packages\System.Buffers.4.5.1\lib\net461\System.Buffers.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="System.ComponentModel.Composition" />
|
||||||
|
<Reference Include="System.Console, Version=4.0.1.1, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||||
|
<HintPath>packages\System.Console.4.3.1\lib\net46\System.Console.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="System.Core" />
|
||||||
|
<Reference Include="System.Diagnostics.DiagnosticSource, Version=7.0.0.1, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
|
||||||
|
<HintPath>packages\System.Diagnostics.DiagnosticSource.7.0.1\lib\net462\System.Diagnostics.DiagnosticSource.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="System.Diagnostics.Tracing, Version=4.1.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||||
|
<HintPath>packages\System.Diagnostics.Tracing.4.3.0\lib\net462\System.Diagnostics.Tracing.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="System.Globalization.Calendars, Version=4.0.2.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||||
|
<HintPath>packages\System.Globalization.Calendars.4.3.0\lib\net46\System.Globalization.Calendars.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="System.IO, Version=4.1.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||||
|
<HintPath>packages\System.IO.4.3.0\lib\net462\System.IO.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="System.IO.Compression, Version=4.1.2.0, Culture=neutral, PublicKeyToken=b77a5c561934e089, processorArchitecture=MSIL">
|
||||||
|
<HintPath>packages\System.IO.Compression.4.3.0\lib\net46\System.IO.Compression.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="System.IO.Compression.FileSystem" />
|
||||||
|
<Reference Include="System.IO.Compression.ZipFile, Version=4.0.2.0, Culture=neutral, PublicKeyToken=b77a5c561934e089, processorArchitecture=MSIL">
|
||||||
|
<HintPath>packages\System.IO.Compression.ZipFile.4.3.0\lib\net46\System.IO.Compression.ZipFile.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="System.IO.FileSystem, Version=4.0.2.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||||
|
<HintPath>packages\System.IO.FileSystem.4.3.0\lib\net46\System.IO.FileSystem.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="System.IO.FileSystem.Primitives, Version=4.0.2.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||||
|
<HintPath>packages\System.IO.FileSystem.Primitives.4.3.0\lib\net46\System.IO.FileSystem.Primitives.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="System.Linq, Version=4.1.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||||
|
<HintPath>packages\System.Linq.4.3.0\lib\net463\System.Linq.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="System.Linq.Expressions, Version=4.1.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||||
|
<HintPath>packages\System.Linq.Expressions.4.3.0\lib\net463\System.Linq.Expressions.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="System.Memory, Version=4.0.1.2, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
|
||||||
|
<HintPath>packages\System.Memory.4.5.5\lib\net461\System.Memory.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="System.Net.Http, Version=4.1.1.3, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||||
|
<HintPath>packages\System.Net.Http.4.3.4\lib\net46\System.Net.Http.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="System.Net.Sockets, Version=4.1.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||||
|
<HintPath>packages\System.Net.Sockets.4.3.0\lib\net46\System.Net.Sockets.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="System.Numerics" />
|
||||||
|
<Reference Include="System.Numerics.Vectors, Version=4.1.4.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||||
|
<HintPath>packages\System.Numerics.Vectors.4.5.0\lib\net46\System.Numerics.Vectors.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="System.Reflection, Version=4.1.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||||
|
<HintPath>packages\System.Reflection.4.3.0\lib\net462\System.Reflection.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="System.Runtime, Version=4.1.1.1, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||||
|
<HintPath>packages\System.Runtime.4.3.1\lib\net462\System.Runtime.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="System.Runtime.CompilerServices.Unsafe, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||||
|
<HintPath>packages\System.Runtime.CompilerServices.Unsafe.6.0.0\lib\net461\System.Runtime.CompilerServices.Unsafe.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="System.Runtime.Extensions, Version=4.1.1.1, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||||
|
<HintPath>packages\System.Runtime.Extensions.4.3.1\lib\net462\System.Runtime.Extensions.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="System.Runtime.InteropServices, Version=4.1.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||||
|
<HintPath>packages\System.Runtime.InteropServices.4.3.0\lib\net463\System.Runtime.InteropServices.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="System.Runtime.InteropServices.RuntimeInformation, Version=4.0.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||||
|
<HintPath>packages\System.Runtime.InteropServices.RuntimeInformation.4.3.0\lib\net45\System.Runtime.InteropServices.RuntimeInformation.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="System.Security.Cryptography.Algorithms, Version=4.2.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||||
|
<HintPath>packages\System.Security.Cryptography.Algorithms.4.3.1\lib\net463\System.Security.Cryptography.Algorithms.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="System.Security.Cryptography.Encoding, Version=4.0.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||||
|
<HintPath>packages\System.Security.Cryptography.Encoding.4.3.0\lib\net46\System.Security.Cryptography.Encoding.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="System.Security.Cryptography.Primitives, Version=4.0.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||||
|
<HintPath>packages\System.Security.Cryptography.Primitives.4.3.0\lib\net46\System.Security.Cryptography.Primitives.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="System.Security.Cryptography.X509Certificates, Version=4.1.1.2, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||||
|
<HintPath>packages\System.Security.Cryptography.X509Certificates.4.3.2\lib\net461\System.Security.Cryptography.X509Certificates.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="System.Text.RegularExpressions, Version=4.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||||
|
<HintPath>packages\System.Text.RegularExpressions.4.3.1\lib\net463\System.Text.RegularExpressions.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="System.Xml.Linq" />
|
||||||
|
<Reference Include="System.Data.DataSetExtensions" />
|
||||||
|
<Reference Include="Microsoft.CSharp" />
|
||||||
|
<Reference Include="System.Data" />
|
||||||
|
<Reference Include="System.Deployment" />
|
||||||
|
<Reference Include="System.Drawing" />
|
||||||
|
<Reference Include="System.Windows.Forms" />
|
||||||
|
<Reference Include="System.Xml" />
|
||||||
|
<Reference Include="System.Xml.ReaderWriter, Version=4.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||||
|
<HintPath>packages\System.Xml.ReaderWriter.4.3.1\lib\net46\System.Xml.ReaderWriter.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Include="DownloadForm.cs">
|
||||||
|
<SubType>Form</SubType>
|
||||||
|
</Compile>
|
||||||
|
<Compile Include="DownloadForm.Designer.cs">
|
||||||
|
<DependentUpon>DownloadForm.cs</DependentUpon>
|
||||||
|
</Compile>
|
||||||
|
<Compile Include="Program.cs" />
|
||||||
|
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||||
|
<Compile Include="Version.cs" />
|
||||||
|
<EmbeddedResource Include="DownloadForm.resx">
|
||||||
|
<DependentUpon>DownloadForm.cs</DependentUpon>
|
||||||
|
</EmbeddedResource>
|
||||||
|
<EmbeddedResource Include="Properties\Resources.resx">
|
||||||
|
<Generator>ResXFileCodeGenerator</Generator>
|
||||||
|
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
|
||||||
|
<SubType>Designer</SubType>
|
||||||
|
</EmbeddedResource>
|
||||||
|
<Compile Include="Properties\Resources.Designer.cs">
|
||||||
|
<AutoGen>True</AutoGen>
|
||||||
|
<DependentUpon>Resources.resx</DependentUpon>
|
||||||
|
</Compile>
|
||||||
|
<None Include="..\..\public\favicon.ico">
|
||||||
|
<Link>favicon.ico</Link>
|
||||||
|
</None>
|
||||||
|
<None Include="packages.config" />
|
||||||
|
<None Include="Properties\Settings.settings">
|
||||||
|
<Generator>SettingsSingleFileGenerator</Generator>
|
||||||
|
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
|
||||||
|
</None>
|
||||||
|
<Compile Include="Properties\Settings.Designer.cs">
|
||||||
|
<AutoGen>True</AutoGen>
|
||||||
|
<DependentUpon>Settings.settings</DependentUpon>
|
||||||
|
<DesignTimeSharedInput>True</DesignTimeSharedInput>
|
||||||
|
</Compile>
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<None Include="App.config" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Content Include=".gitignore" />
|
||||||
|
<Content Include="app.manifest" />
|
||||||
|
</ItemGroup>
|
||||||
|
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||||
|
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
|
||||||
|
<PropertyGroup>
|
||||||
|
<ErrorText>This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105.The missing file is {0}.</ErrorText>
|
||||||
|
</PropertyGroup>
|
||||||
|
<Error Condition="!Exists('packages\Costura.Fody.5.7.0\build\Costura.Fody.props')" Text="$([System.String]::Format('$(ErrorText)', 'packages\Costura.Fody.5.7.0\build\Costura.Fody.props'))" />
|
||||||
|
<Error Condition="!Exists('packages\Costura.Fody.5.7.0\build\Costura.Fody.targets')" Text="$([System.String]::Format('$(ErrorText)', 'packages\Costura.Fody.5.7.0\build\Costura.Fody.targets'))" />
|
||||||
|
<Error Condition="!Exists('packages\Fody.6.6.4\build\Fody.targets')" Text="$([System.String]::Format('$(ErrorText)', 'packages\Fody.6.6.4\build\Fody.targets'))" />
|
||||||
|
<Error Condition="!Exists('packages\NETStandard.Library.2.0.3\build\netstandard2.0\NETStandard.Library.targets')" Text="$([System.String]::Format('$(ErrorText)', 'packages\NETStandard.Library.2.0.3\build\netstandard2.0\NETStandard.Library.targets'))" />
|
||||||
|
</Target>
|
||||||
|
<Import Project="packages\Costura.Fody.5.7.0\build\Costura.Fody.targets" Condition="Exists('packages\Costura.Fody.5.7.0\build\Costura.Fody.targets')" />
|
||||||
|
<Import Project="packages\Fody.6.6.4\build\Fody.targets" Condition="Exists('packages\Fody.6.6.4\build\Fody.targets')" />
|
||||||
|
<Import Project="packages\NETStandard.Library.2.0.3\build\netstandard2.0\NETStandard.Library.targets" Condition="Exists('packages\NETStandard.Library.2.0.3\build\netstandard2.0\NETStandard.Library.targets')" />
|
||||||
|
</Project>
|
16
extra/exe-builder/UptimeKuma.sln
Normal file
16
extra/exe-builder/UptimeKuma.sln
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
|
||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UptimeKuma", "UptimeKuma.csproj", "{2DB53988-1D93-4AC0-90C4-96ADEAAC5C04}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Release|Any CPU = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{2DB53988-1D93-4AC0-90C4-96ADEAAC5C04}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{2DB53988-1D93-4AC0-90C4-96ADEAAC5C04}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{2DB53988-1D93-4AC0-90C4-96ADEAAC5C04}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{2DB53988-1D93-4AC0-90C4-96ADEAAC5C04}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
3
extra/exe-builder/UptimeKuma.sln.DotSettings.user
Normal file
3
extra/exe-builder/UptimeKuma.sln.DotSettings.user
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||||
|
<s:Boolean x:Key="/Default/ResxEditorPersonal/CheckedGroups/=UptimeKuma_002FProperties_002FResources/@EntryIndexedValue">True</s:Boolean>
|
||||||
|
<s:Boolean x:Key="/Default/ResxEditorPersonal/Initialized/@EntryValue">True</s:Boolean></wpf:ResourceDictionary>
|
9
extra/exe-builder/Version.cs
Normal file
9
extra/exe-builder/Version.cs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
namespace UptimeKuma {
|
||||||
|
public class Version {
|
||||||
|
public string latest { get; set; }
|
||||||
|
public string slow { get; set; }
|
||||||
|
public string beta { get; set; }
|
||||||
|
public string nodejs { get; set; }
|
||||||
|
public string exe { get; set; }
|
||||||
|
}
|
||||||
|
}
|
28
extra/exe-builder/app.manifest
Normal file
28
extra/exe-builder/app.manifest
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||||
|
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
|
||||||
|
<asmv3:application>
|
||||||
|
<asmv3:windowsSettings>
|
||||||
|
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware>
|
||||||
|
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
|
||||||
|
</asmv3:windowsSettings>
|
||||||
|
</asmv3:application>
|
||||||
|
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
|
||||||
|
<security>
|
||||||
|
<requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||||
|
<!-- UAC Manifest Options
|
||||||
|
If you want to change the Windows User Account Control level replace the
|
||||||
|
requestedExecutionLevel node with one of the following.
|
||||||
|
|
||||||
|
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
|
||||||
|
<requestedExecutionLevel level="requireAdministrator" uiAccess="false" />
|
||||||
|
<requestedExecutionLevel level="highestAvailable" uiAccess="false" />
|
||||||
|
|
||||||
|
Specifying requestedExecutionLevel element will disable file and registry virtualization.
|
||||||
|
Remove this element if your application requires this virtualization for backwards
|
||||||
|
compatibility.
|
||||||
|
-->
|
||||||
|
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
|
||||||
|
</requestedPrivileges>
|
||||||
|
</security>
|
||||||
|
</trustInfo>
|
||||||
|
</assembly>
|
56
extra/exe-builder/packages.config
Normal file
56
extra/exe-builder/packages.config
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<packages>
|
||||||
|
<package id="Costura.Fody" version="5.7.0" targetFramework="net472" developmentDependency="true" />
|
||||||
|
<package id="Fody" version="6.6.4" targetFramework="net472" developmentDependency="true" />
|
||||||
|
<package id="Microsoft.NETCore.Platforms" version="7.0.0" targetFramework="net472" />
|
||||||
|
<package id="Microsoft.Win32.Primitives" version="4.3.0" targetFramework="net472" />
|
||||||
|
<package id="NETStandard.Library" version="2.0.3" targetFramework="net472" />
|
||||||
|
<package id="Newtonsoft.Json" version="13.0.2" targetFramework="net472" />
|
||||||
|
<package id="System.AppContext" version="4.3.0" targetFramework="net472" />
|
||||||
|
<package id="System.Console" version="4.3.1" targetFramework="net472" />
|
||||||
|
<package id="System.Diagnostics.DiagnosticSource" version="7.0.1" targetFramework="net472" />
|
||||||
|
<package id="System.Net.Http" version="4.3.4" targetFramework="net472" />
|
||||||
|
<package id="System.Runtime.Extensions" version="4.3.1" targetFramework="net472" />
|
||||||
|
<package id="System.Security.Cryptography.Algorithms" version="4.3.1" targetFramework="net472" />
|
||||||
|
<package id="System.Security.Cryptography.X509Certificates" version="4.3.2" targetFramework="net472" />
|
||||||
|
<package id="System.Text.RegularExpressions" version="4.3.1" targetFramework="net472" />
|
||||||
|
<package id="System.Xml.ReaderWriter" version="4.3.1" targetFramework="net472" />
|
||||||
|
<package id="System.Memory" version="4.5.5" targetFramework="net472" />
|
||||||
|
<package id="System.Net.Primitives" version="4.3.1" targetFramework="net472" />
|
||||||
|
<package id="System.Runtime" version="4.3.1" targetFramework="net472" />
|
||||||
|
<package id="System.Buffers" version="4.5.1" targetFramework="net472" />
|
||||||
|
<package id="System.Collections" version="4.3.0" targetFramework="net472" />
|
||||||
|
<package id="System.Collections.Concurrent" version="4.3.0" targetFramework="net472" />
|
||||||
|
<package id="System.Diagnostics.Debug" version="4.3.0" targetFramework="net472" />
|
||||||
|
<package id="System.Diagnostics.Tools" version="4.3.0" targetFramework="net472" />
|
||||||
|
<package id="System.Diagnostics.Tracing" version="4.3.0" targetFramework="net472" />
|
||||||
|
<package id="System.Globalization" version="4.3.0" targetFramework="net472" />
|
||||||
|
<package id="System.Globalization.Calendars" version="4.3.0" targetFramework="net472" />
|
||||||
|
<package id="System.IO" version="4.3.0" targetFramework="net472" />
|
||||||
|
<package id="System.IO.Compression" version="4.3.0" targetFramework="net472" />
|
||||||
|
<package id="System.IO.Compression.ZipFile" version="4.3.0" targetFramework="net472" />
|
||||||
|
<package id="System.IO.FileSystem" version="4.3.0" targetFramework="net472" />
|
||||||
|
<package id="System.IO.FileSystem.Primitives" version="4.3.0" targetFramework="net472" />
|
||||||
|
<package id="System.Linq" version="4.3.0" targetFramework="net472" />
|
||||||
|
<package id="System.Linq.Expressions" version="4.3.0" targetFramework="net472" />
|
||||||
|
<package id="System.Net.Sockets" version="4.3.0" targetFramework="net472" />
|
||||||
|
<package id="System.Numerics.Vectors" version="4.5.0" targetFramework="net472" />
|
||||||
|
<package id="System.ObjectModel" version="4.3.0" targetFramework="net472" />
|
||||||
|
<package id="System.Reflection" version="4.3.0" targetFramework="net472" />
|
||||||
|
<package id="System.Reflection.Extensions" version="4.3.0" targetFramework="net472" />
|
||||||
|
<package id="System.Reflection.Primitives" version="4.3.0" targetFramework="net472" />
|
||||||
|
<package id="System.Resources.ResourceManager" version="4.3.0" targetFramework="net472" />
|
||||||
|
<package id="System.Runtime.CompilerServices.Unsafe" version="6.0.0" targetFramework="net472" />
|
||||||
|
<package id="System.Runtime.Handles" version="4.3.0" targetFramework="net472" />
|
||||||
|
<package id="System.Runtime.InteropServices" version="4.3.0" targetFramework="net472" />
|
||||||
|
<package id="System.Runtime.InteropServices.RuntimeInformation" version="4.3.0" targetFramework="net472" />
|
||||||
|
<package id="System.Runtime.Numerics" version="4.3.0" targetFramework="net472" />
|
||||||
|
<package id="System.Security.Cryptography.Encoding" version="4.3.0" targetFramework="net472" />
|
||||||
|
<package id="System.Security.Cryptography.Primitives" version="4.3.0" targetFramework="net472" />
|
||||||
|
<package id="System.Text.Encoding" version="4.3.0" targetFramework="net472" />
|
||||||
|
<package id="System.Text.Encoding.Extensions" version="4.3.0" targetFramework="net472" />
|
||||||
|
<package id="System.Threading" version="4.3.0" targetFramework="net472" />
|
||||||
|
<package id="System.Threading.Tasks" version="4.3.0" targetFramework="net472" />
|
||||||
|
<package id="System.Threading.Timer" version="4.3.0" targetFramework="net472" />
|
||||||
|
<package id="System.Xml.XDocument" version="4.3.0" targetFramework="net472" />
|
||||||
|
</packages>
|
90
extra/healthcheck.go
Normal file
90
extra/healthcheck.go
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
/*
|
||||||
|
* If changed, have to run `npm run build-docker-builder-go`.
|
||||||
|
* This script should be run after a period of time (180s), because the server may need some time to prepare.
|
||||||
|
*/
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
isFreeBSD := runtime.GOOS == "freebsd"
|
||||||
|
|
||||||
|
// Is K8S + uptime-kuma as the container name
|
||||||
|
// See #2083
|
||||||
|
isK8s := strings.HasPrefix(os.Getenv("UPTIME_KUMA_PORT"), "tcp://")
|
||||||
|
|
||||||
|
// process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
|
||||||
|
http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{
|
||||||
|
InsecureSkipVerify: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
client := http.Client{
|
||||||
|
Timeout: 28 * time.Second,
|
||||||
|
}
|
||||||
|
|
||||||
|
sslKey := os.Getenv("UPTIME_KUMA_SSL_KEY")
|
||||||
|
if len(sslKey) == 0 {
|
||||||
|
sslKey = os.Getenv("SSL_KEY")
|
||||||
|
}
|
||||||
|
|
||||||
|
sslCert := os.Getenv("UPTIME_KUMA_SSL_CERT")
|
||||||
|
if len(sslCert) == 0 {
|
||||||
|
sslCert = os.Getenv("SSL_CERT")
|
||||||
|
}
|
||||||
|
|
||||||
|
hostname := os.Getenv("UPTIME_KUMA_HOST")
|
||||||
|
if len(hostname) == 0 && !isFreeBSD {
|
||||||
|
hostname = os.Getenv("HOST")
|
||||||
|
}
|
||||||
|
if len(hostname) == 0 {
|
||||||
|
hostname = "127.0.0.1"
|
||||||
|
}
|
||||||
|
|
||||||
|
port := ""
|
||||||
|
// UPTIME_KUMA_PORT is override by K8S unexpectedly,
|
||||||
|
if !isK8s {
|
||||||
|
port = os.Getenv("UPTIME_KUMA_PORT")
|
||||||
|
}
|
||||||
|
if len(port) == 0 {
|
||||||
|
port = os.Getenv("PORT")
|
||||||
|
}
|
||||||
|
if len(port) == 0 {
|
||||||
|
port = "3001"
|
||||||
|
}
|
||||||
|
|
||||||
|
protocol := ""
|
||||||
|
if len(sslKey) != 0 && len(sslCert) != 0 {
|
||||||
|
protocol = "https"
|
||||||
|
} else {
|
||||||
|
protocol = "http"
|
||||||
|
}
|
||||||
|
|
||||||
|
url := protocol + "://" + hostname + ":" + port
|
||||||
|
|
||||||
|
log.Println("Checking " + url)
|
||||||
|
resp, err := client.Get(url)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
_, err = ioutil.ReadAll(resp.Body)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("Health Check OK [Res Code: %d]\n", resp.StatusCode)
|
||||||
|
|
||||||
|
}
|
@@ -1,4 +1,9 @@
|
|||||||
/*
|
/*
|
||||||
|
* ⚠️ ⚠️ ⚠️ ⚠️ Due to the weird issue in Portainer that the healthcheck script is still pointing to this script for unknown reason.
|
||||||
|
* IT CANNOT BE DROPPED, even though it looks like it is not used.
|
||||||
|
* See more: https://github.com/louislam/uptime-kuma/issues/2774#issuecomment-1429092359
|
||||||
|
*
|
||||||
|
* ⚠️ Deprecated: Changed to healthcheck.go, it will be deleted in the future.
|
||||||
* This script should be run after a period of time (180s), because the server may need some time to prepare.
|
* 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");
|
const { FBSD } = require("../server/util-server");
|
||||||
|
@@ -1,11 +1,12 @@
|
|||||||
const pkg = require("../package.json");
|
const pkg = require("../package.json");
|
||||||
const fs = require("fs");
|
const fs = require("fs");
|
||||||
const util = require("../src/util");
|
const util = require("../src/util");
|
||||||
|
const dayjs = require("dayjs");
|
||||||
|
|
||||||
util.polyfill();
|
util.polyfill();
|
||||||
|
|
||||||
const oldVersion = pkg.version;
|
const oldVersion = pkg.version;
|
||||||
const newVersion = oldVersion + "-nightly";
|
const newVersion = oldVersion + "-nightly-" + dayjs().format("YYYYMMDDHHmmss");
|
||||||
|
|
||||||
console.log("Old Version: " + oldVersion);
|
console.log("Old Version: " + oldVersion);
|
||||||
console.log("New Version: " + newVersion);
|
console.log("New Version: " + newVersion);
|
||||||
|
@@ -43,6 +43,11 @@ const main = async () => {
|
|||||||
console.log("Finished.");
|
console.log("Finished.");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ask question of user
|
||||||
|
* @param {string} question Question to ask
|
||||||
|
* @returns {Promise<string>} Users response
|
||||||
|
*/
|
||||||
function question(question) {
|
function question(question) {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
rl.question(question, (answer) => {
|
rl.question(question, (answer) => {
|
||||||
|
@@ -53,6 +53,11 @@ const main = async () => {
|
|||||||
console.log("Finished.");
|
console.log("Finished.");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ask question of user
|
||||||
|
* @param {string} question Question to ask
|
||||||
|
* @returns {Promise<string>} Users response
|
||||||
|
*/
|
||||||
function question(question) {
|
function question(question) {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
rl.question(question, (answer) => {
|
rl.question(question, (answer) => {
|
||||||
|
@@ -135,6 +135,11 @@ server.listen({
|
|||||||
udp: 5300
|
udp: 5300
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get human readable request type from request code
|
||||||
|
* @param {number} code Request code to translate
|
||||||
|
* @returns {string} Human readable request type
|
||||||
|
*/
|
||||||
function type(code) {
|
function type(code) {
|
||||||
for (let name in Packet.TYPE) {
|
for (let name in Packet.TYPE) {
|
||||||
if (Packet.TYPE[name] === code) {
|
if (Packet.TYPE[name] === code) {
|
||||||
|
@@ -11,6 +11,7 @@ class SimpleMqttServer {
|
|||||||
this.port = port;
|
this.port = port;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Start the MQTT server */
|
||||||
start() {
|
start() {
|
||||||
this.server.listen(this.port, () => {
|
this.server.listen(this.port, () => {
|
||||||
console.log("server started and listening on port ", this.port);
|
console.log("server started and listening on port ", this.port);
|
||||||
|
22
extra/sort-contributors.js
Normal file
22
extra/sort-contributors.js
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
const fs = require("fs");
|
||||||
|
|
||||||
|
// Read the file from private/sort-contributors.txt
|
||||||
|
const file = fs.readFileSync("private/sort-contributors.txt", "utf8");
|
||||||
|
|
||||||
|
// Convert to an array of lines
|
||||||
|
let lines = file.split("\n");
|
||||||
|
|
||||||
|
// Remove empty lines
|
||||||
|
lines = lines.filter((line) => line !== "");
|
||||||
|
|
||||||
|
// Remove duplicates
|
||||||
|
lines = [ ...new Set(lines) ];
|
||||||
|
|
||||||
|
// Remove @weblate and @UptimeKumaBot
|
||||||
|
lines = lines.filter((line) => line !== "@weblate" && line !== "@UptimeKumaBot" && line !== "@louislam");
|
||||||
|
|
||||||
|
// Sort the lines
|
||||||
|
lines = lines.sort();
|
||||||
|
|
||||||
|
// Output the lines, concat with " "
|
||||||
|
console.log(lines.join(" "));
|
@@ -1,51 +1,45 @@
|
|||||||
// Need to use ES6 to read language files
|
// Need to use ES6 to read language files
|
||||||
|
|
||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
import path from "path";
|
|
||||||
import util from "util";
|
import util from "util";
|
||||||
import rmSync from "../fs-rmSync.js";
|
import rmSync from "../fs-rmSync.js";
|
||||||
|
|
||||||
// https://stackoverflow.com/questions/13786160/copy-folder-recursively-in-node-js
|
|
||||||
/**
|
/**
|
||||||
* Look ma, it's cp -R.
|
* Copy across the required language files
|
||||||
* @param {string} src The path to the thing to copy.
|
* Creates a local directory (./languages) and copies the required files
|
||||||
* @param {string} dest The path to the new copy.
|
* into it.
|
||||||
|
* @param {string} langCode Code of language to update. A file will be
|
||||||
|
* created with this code if one does not already exist
|
||||||
|
* @param {string} baseLang The second base language file to copy. This
|
||||||
|
* will be ignored if set to "en" as en.js is copied by default
|
||||||
*/
|
*/
|
||||||
const copyRecursiveSync = function (src, dest) {
|
function copyFiles(langCode, baseLang) {
|
||||||
let exists = fs.existsSync(src);
|
if (fs.existsSync("./languages")) {
|
||||||
let stats = exists && fs.statSync(src);
|
rmSync("./languages", { recursive: true });
|
||||||
let isDirectory = exists && stats.isDirectory();
|
}
|
||||||
|
fs.mkdirSync("./languages");
|
||||||
|
|
||||||
if (isDirectory) {
|
if (!fs.existsSync(`../../src/languages/${langCode}.js`)) {
|
||||||
fs.mkdirSync(dest);
|
fs.closeSync(fs.openSync(`./languages/${langCode}.js`, "a"));
|
||||||
fs.readdirSync(src).forEach(function (childItemName) {
|
|
||||||
copyRecursiveSync(path.join(src, childItemName),
|
|
||||||
path.join(dest, childItemName));
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
fs.copyFileSync(src, dest);
|
fs.copyFileSync(`../../src/languages/${langCode}.js`, `./languages/${langCode}.js`);
|
||||||
|
}
|
||||||
|
fs.copyFileSync("../../src/languages/en.js", "./languages/en.js");
|
||||||
|
if (baseLang !== "en") {
|
||||||
|
fs.copyFileSync(`../../src/languages/${baseLang}.js`, `./languages/${baseLang}.js`);
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
console.log("Arguments:", process.argv);
|
|
||||||
const baseLangCode = process.argv[2] || "en";
|
|
||||||
console.log("Base Lang: " + baseLangCode);
|
|
||||||
if (fs.existsSync("./languages")) {
|
|
||||||
rmSync("./languages", { recursive: true });
|
|
||||||
}
|
}
|
||||||
copyRecursiveSync("../../src/languages", "./languages");
|
|
||||||
|
|
||||||
const en = (await import("./languages/en.js")).default;
|
/**
|
||||||
const baseLang = (await import(`./languages/${baseLangCode}.js`)).default;
|
* Update the specified language file
|
||||||
const files = fs.readdirSync("./languages");
|
* @param {string} langCode Language code to update
|
||||||
console.log("Files:", files);
|
* @param {string} baseLang Second language to copy keys from
|
||||||
|
*/
|
||||||
for (const file of files) {
|
async function updateLanguage(langCode, baseLangCode) {
|
||||||
if (! file.endsWith(".js")) {
|
const en = (await import("./languages/en.js")).default;
|
||||||
console.log("Skipping " + file);
|
const baseLang = (await import(`./languages/${baseLangCode}.js`)).default;
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
let file = langCode + ".js";
|
||||||
console.log("Processing " + file);
|
console.log("Processing " + file);
|
||||||
const lang = await import("./languages/" + file);
|
const lang = await import("./languages/" + file);
|
||||||
|
|
||||||
@@ -83,5 +77,20 @@ for (const file of files) {
|
|||||||
fs.writeFileSync(`../../src/languages/${file}`, code);
|
fs.writeFileSync(`../../src/languages/${file}`, code);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get command line arguments
|
||||||
|
const baseLangCode = process.env.npm_config_baselang || "en";
|
||||||
|
const langCode = process.env.npm_config_language;
|
||||||
|
|
||||||
|
// We need the file to edit
|
||||||
|
if (langCode == null) {
|
||||||
|
throw new Error("Argument --language=<code> must be provided");
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("Base Lang: " + baseLangCode);
|
||||||
|
console.log("Updating: " + langCode);
|
||||||
|
|
||||||
|
copyFiles(langCode, baseLangCode);
|
||||||
|
await updateLanguage(langCode, baseLangCode);
|
||||||
rmSync("./languages", { recursive: true });
|
rmSync("./languages", { recursive: true });
|
||||||
|
|
||||||
console.log("Done. Fixing formatting by ESLint...");
|
console.log("Done. Fixing formatting by ESLint...");
|
||||||
|
@@ -26,7 +26,8 @@ if (! exists) {
|
|||||||
fs.writeFileSync("package.json", JSON.stringify(pkg, null, 4) + "\n");
|
fs.writeFileSync("package.json", JSON.stringify(pkg, null, 4) + "\n");
|
||||||
|
|
||||||
// Also update package-lock.json
|
// Also update package-lock.json
|
||||||
childProcess.spawnSync("npm", [ "install" ]);
|
const npm = /^win/.test(process.platform) ? "npm.cmd" : "npm";
|
||||||
|
childProcess.spawnSync(npm, [ "install" ]);
|
||||||
|
|
||||||
commit(newVersion);
|
commit(newVersion);
|
||||||
tag(newVersion);
|
tag(newVersion);
|
||||||
@@ -36,10 +37,8 @@ if (! exists) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates the version number in package.json and commits it to git.
|
* Commit updated files
|
||||||
* @param {string} version - The new version number
|
* @param {string} version Version to update to
|
||||||
*
|
|
||||||
* Generated by Trelent
|
|
||||||
*/
|
*/
|
||||||
function commit(version) {
|
function commit(version) {
|
||||||
let msg = "Update to " + version;
|
let msg = "Update to " + version;
|
||||||
@@ -53,16 +52,19 @@ function commit(version) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a tag with the specified version
|
||||||
|
* @param {string} version Tag to create
|
||||||
|
*/
|
||||||
function tag(version) {
|
function tag(version) {
|
||||||
let res = childProcess.spawnSync("git", [ "tag", version ]);
|
let res = childProcess.spawnSync("git", [ "tag", version ]);
|
||||||
console.log(res.stdout.toString().trim());
|
console.log(res.stdout.toString().trim());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if a given version is already tagged in the git repository.
|
* Check if a tag exists for the specified version
|
||||||
* @param {string} version - The version to check for.
|
* @param {string} version Version to check
|
||||||
*
|
* @returns {boolean} Does the tag already exist
|
||||||
* Generated by Trelent
|
|
||||||
*/
|
*/
|
||||||
function tagExists(version) {
|
function tagExists(version) {
|
||||||
if (! version) {
|
if (! version) {
|
||||||
|
@@ -10,6 +10,10 @@ if (!newVersion) {
|
|||||||
|
|
||||||
updateWiki(newVersion);
|
updateWiki(newVersion);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the wiki with new version number
|
||||||
|
* @param {string} newVersion Version to update to
|
||||||
|
*/
|
||||||
function updateWiki(newVersion) {
|
function updateWiki(newVersion) {
|
||||||
const wikiDir = "./tmp/wiki";
|
const wikiDir = "./tmp/wiki";
|
||||||
const howToUpdateFilename = "./tmp/wiki/🆙-How-to-Update.md";
|
const howToUpdateFilename = "./tmp/wiki/🆙-How-to-Update.md";
|
||||||
@@ -39,6 +43,10 @@ function updateWiki(newVersion) {
|
|||||||
safeDelete(wikiDir);
|
safeDelete(wikiDir);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a directory exists and then delete it
|
||||||
|
* @param {string} dir Directory to delete
|
||||||
|
*/
|
||||||
function safeDelete(dir) {
|
function safeDelete(dir) {
|
||||||
if (fs.existsSync(dir)) {
|
if (fs.existsSync(dir)) {
|
||||||
fs.rm(dir, {
|
fs.rm(dir, {
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
|
||||||
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">
|
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">
|
||||||
<link rel="icon" type="image/svg+xml" href="/icon.svg" />
|
<link rel="icon" type="image/svg+xml" href="/icon.svg" />
|
||||||
<link rel="manifest" href="/manifest.json" />
|
<link rel="manifest" href="/manifest.json" />
|
||||||
|
20973
package-lock.json
generated
20973
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
105
package.json
105
package.json
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "uptime-kuma",
|
"name": "uptime-kuma",
|
||||||
"version": "1.18.3",
|
"version": "1.22.0-beta.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@@ -31,6 +31,7 @@
|
|||||||
"build-docker": "npm run build && npm run build-docker-debian && npm run build-docker-alpine",
|
"build-docker": "npm run build && npm run build-docker-debian && npm run build-docker-alpine",
|
||||||
"build-docker-alpine-base": "docker buildx build -f docker/alpine-base.dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:base-alpine . --push",
|
"build-docker-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-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-builder-go": "docker buildx build -f docker/builder-go.dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:builder-go . --push",
|
||||||
"build-docker-alpine": "node ./extra/env2arg.js docker buildx build -f docker/dockerfile-alpine --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:alpine -t louislam/uptime-kuma:1-alpine -t louislam/uptime-kuma:$VERSION-alpine --target release . --push",
|
"build-docker-alpine": "node ./extra/env2arg.js docker buildx build -f docker/dockerfile-alpine --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:alpine -t louislam/uptime-kuma:1-alpine -t louislam/uptime-kuma:$VERSION-alpine --target release . --push",
|
||||||
"build-docker-debian": "node ./extra/env2arg.js docker buildx build -f docker/dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma -t louislam/uptime-kuma:1 -t louislam/uptime-kuma:$VERSION -t louislam/uptime-kuma:debian -t louislam/uptime-kuma:1-debian -t louislam/uptime-kuma:$VERSION-debian --target release . --push",
|
"build-docker-debian": "node ./extra/env2arg.js docker buildx build -f docker/dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma -t louislam/uptime-kuma:1 -t louislam/uptime-kuma:$VERSION -t louislam/uptime-kuma:debian -t louislam/uptime-kuma:1-debian -t louislam/uptime-kuma:$VERSION-debian --target release . --push",
|
||||||
"build-docker-nightly": "npm run build && docker buildx build -f docker/dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:nightly --target nightly . --push",
|
"build-docker-nightly": "npm run build && docker buildx build -f docker/dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:nightly --target nightly . --push",
|
||||||
@@ -38,7 +39,7 @@
|
|||||||
"build-docker-nightly-amd64": "docker buildx build -f docker/dockerfile --platform linux/amd64 -t louislam/uptime-kuma:nightly-amd64 --target nightly . --push --progress plain",
|
"build-docker-nightly-amd64": "docker buildx build -f docker/dockerfile --platform linux/amd64 -t louislam/uptime-kuma:nightly-amd64 --target nightly . --push --progress plain",
|
||||||
"build-docker-pr-test": "docker buildx build -f docker/dockerfile --platform linux/amd64,linux/arm64 -t louislam/uptime-kuma:pr-test --target pr-test . --push",
|
"build-docker-pr-test": "docker buildx build -f docker/dockerfile --platform linux/amd64,linux/arm64 -t louislam/uptime-kuma:pr-test --target pr-test . --push",
|
||||||
"upload-artifacts": "docker buildx build -f docker/dockerfile --platform linux/amd64 -t louislam/uptime-kuma:upload-artifact --build-arg VERSION --build-arg GITHUB_TOKEN --target upload-artifact . --progress plain",
|
"upload-artifacts": "docker buildx build -f docker/dockerfile --platform linux/amd64 -t louislam/uptime-kuma:upload-artifact --build-arg VERSION --build-arg GITHUB_TOKEN --target upload-artifact . --progress plain",
|
||||||
"setup": "git checkout 1.18.3 && npm ci --production && npm run download-dist",
|
"setup": "git checkout 1.21.3 && npm ci --production && npm run download-dist",
|
||||||
"download-dist": "node extra/download-dist.js",
|
"download-dist": "node extra/download-dist.js",
|
||||||
"mark-as-nightly": "node extra/mark-as-nightly.js",
|
"mark-as-nightly": "node extra/mark-as-nightly.js",
|
||||||
"reset-password": "node extra/reset-password.js",
|
"reset-password": "node extra/reset-password.js",
|
||||||
@@ -51,8 +52,7 @@
|
|||||||
"test-nodejs16": "docker build --progress plain -f test/ubuntu-nodejs16.dockerfile .",
|
"test-nodejs16": "docker build --progress plain -f test/ubuntu-nodejs16.dockerfile .",
|
||||||
"simple-dns-server": "node extra/simple-dns-server.js",
|
"simple-dns-server": "node extra/simple-dns-server.js",
|
||||||
"simple-mqtt-server": "node extra/simple-mqtt-server.js",
|
"simple-mqtt-server": "node extra/simple-mqtt-server.js",
|
||||||
"update-language-files-with-base-lang": "cd extra/update-language-files && node index.js %npm_config_base_lang% && eslint ../../src/languages/**.js --fix",
|
"update-language-files": "cd extra/update-language-files && node index.js && cross-env-shell eslint ../../src/languages/$npm_config_language.js --fix",
|
||||||
"update-language-files": "cd extra/update-language-files && node index.js && eslint ../../src/languages/**.js --fix",
|
|
||||||
"ncu-patch": "npm-check-updates -u -t patch",
|
"ncu-patch": "npm-check-updates -u -t patch",
|
||||||
"release-final": "node extra/update-version.js && npm run build-docker && node ./extra/press-any-key.js && npm run upload-artifacts && node ./extra/update-wiki-version.js",
|
"release-final": "node extra/update-version.js && npm run build-docker && node ./extra/press-any-key.js && npm run upload-artifacts && node ./extra/update-wiki-version.js",
|
||||||
"release-beta": "node extra/beta/update-version.js && npm run build && node ./extra/env2arg.js docker buildx build -f docker/dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:$VERSION -t louislam/uptime-kuma:beta . --target release --push && node ./extra/press-any-key.js && npm run upload-artifacts",
|
"release-beta": "node extra/beta/update-version.js && npm run build && node ./extra/env2arg.js docker buildx build -f docker/dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:$VERSION -t louislam/uptime-kuma:beta . --target release --push && node ./extra/press-any-key.js && npm run upload-artifacts",
|
||||||
@@ -61,52 +61,67 @@
|
|||||||
"start-pr-test": "node extra/checkout-pr.js && npm install && npm run dev",
|
"start-pr-test": "node extra/checkout-pr.js && npm install && npm run dev",
|
||||||
"cy:test": "node test/prepare-test-server.js && node server/server.js --port=3002 --data-dir=./data/test/ --e2e",
|
"cy:test": "node test/prepare-test-server.js && node server/server.js --port=3002 --data-dir=./data/test/ --e2e",
|
||||||
"cy:run": "npx cypress run --browser chrome --headless --config-file ./config/cypress.config.js",
|
"cy:run": "npx cypress run --browser chrome --headless --config-file ./config/cypress.config.js",
|
||||||
"cypress-open": "concurrently -k -r \"node test/prepare-test-server.js && node server/server.js --port=3002 --data-dir=./data/test/\" \"cypress open --config-file ./config/cypress.config.js\""
|
"cy:run:unit": "npx cypress run --browser chrome --headless --config-file ./config/cypress.frontend.config.js",
|
||||||
|
"cypress-open": "concurrently -k -r \"node test/prepare-test-server.js && node server/server.js --port=3002 --data-dir=./data/test/\" \"cypress open --config-file ./config/cypress.config.js\"",
|
||||||
|
"build-healthcheck-armv7": "cross-env GOOS=linux GOARCH=arm GOARM=7 go build -x -o ./extra/healthcheck-armv7 ./extra/healthcheck.go",
|
||||||
|
"deploy-demo-server": "node extra/deploy-demo-server.js",
|
||||||
|
"sort-contributors": "node extra/sort-contributors.js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@louislam/sqlite3": "~15.0.6",
|
"@grpc/grpc-js": "~1.7.3",
|
||||||
|
"@louislam/ping": "~0.4.4-mod.0",
|
||||||
|
"@louislam/sqlite3": "15.1.6",
|
||||||
"args-parser": "~1.3.0",
|
"args-parser": "~1.3.0",
|
||||||
"axios": "~0.27.0",
|
"axios": "~0.27.0",
|
||||||
"axios-ntlm": "^1.3.0",
|
"axios-ntlm": "1.3.0",
|
||||||
"badge-maker": "^3.3.1",
|
"badge-maker": "~3.3.1",
|
||||||
"bcryptjs": "~2.4.3",
|
"bcryptjs": "~2.4.3",
|
||||||
"bree": "~7.1.5",
|
|
||||||
"cacheable-lookup": "~6.0.4",
|
"cacheable-lookup": "~6.0.4",
|
||||||
"chardet": "^1.3.0",
|
"chardet": "~1.4.0",
|
||||||
"check-password-strength": "^2.0.5",
|
"check-password-strength": "^2.0.5",
|
||||||
"cheerio": "^1.0.0-rc.10",
|
"cheerio": "~1.0.0-rc.12",
|
||||||
"chroma-js": "^2.1.2",
|
"chroma-js": "~2.4.2",
|
||||||
"command-exists": "~1.2.9",
|
"command-exists": "~1.2.9",
|
||||||
"compare-versions": "~3.6.0",
|
"compare-versions": "~3.6.0",
|
||||||
"compression": "^1.7.4",
|
"compression": "~1.7.4",
|
||||||
"dayjs": "^1.11.0",
|
"croner": "^6.0.3",
|
||||||
|
"dayjs": "~1.11.5",
|
||||||
|
"dotenv": "~16.0.3",
|
||||||
"express": "~4.17.3",
|
"express": "~4.17.3",
|
||||||
"express-basic-auth": "~1.2.1",
|
"express-basic-auth": "~1.2.1",
|
||||||
"express-static-gzip": "^2.1.7",
|
"express-static-gzip": "~2.1.7",
|
||||||
"form-data": "~4.0.0",
|
"form-data": "~4.0.0",
|
||||||
|
"gamedig": "^4.0.5",
|
||||||
"http-graceful-shutdown": "~3.1.7",
|
"http-graceful-shutdown": "~3.1.7",
|
||||||
"http-proxy-agent": "^5.0.0",
|
"http-proxy-agent": "~5.0.0",
|
||||||
"https-proxy-agent": "^5.0.0",
|
"https-proxy-agent": "~5.0.1",
|
||||||
"iconv-lite": "^0.6.3",
|
"iconv-lite": "~0.6.3",
|
||||||
"jsonwebtoken": "~8.5.1",
|
"jsesc": "~3.0.2",
|
||||||
"jwt-decode": "^3.1.2",
|
"jsonwebtoken": "~9.0.0",
|
||||||
"limiter": "^2.1.0",
|
"jwt-decode": "~3.1.2",
|
||||||
"mqtt": "^4.2.8",
|
"limiter": "~2.1.0",
|
||||||
"mssql": "^8.1.0",
|
"mongodb": "~4.14.0",
|
||||||
|
"mqtt": "~4.3.7",
|
||||||
|
"mssql": "~8.1.4",
|
||||||
|
"mysql2": "~2.3.3",
|
||||||
|
"nanoid": "^3.3.4",
|
||||||
"node-cloudflared-tunnel": "~1.0.9",
|
"node-cloudflared-tunnel": "~1.0.9",
|
||||||
"node-radius-client": "^1.0.0",
|
"node-radius-client": "~1.0.0",
|
||||||
"nodemailer": "~6.6.5",
|
"nodemailer": "~6.6.5",
|
||||||
"notp": "~2.0.3",
|
"notp": "~2.0.3",
|
||||||
"password-hash": "~1.2.2",
|
"password-hash": "~1.2.2",
|
||||||
"pg": "^8.7.3",
|
"pg": "~8.8.0",
|
||||||
"pg-connection-string": "^2.5.0",
|
"pg-connection-string": "~2.5.0",
|
||||||
"prom-client": "~13.2.0",
|
"prom-client": "~13.2.0",
|
||||||
"prometheus-api-metrics": "~3.2.1",
|
"prometheus-api-metrics": "~3.2.1",
|
||||||
"redbean-node": "0.1.4",
|
"protobufjs": "~7.1.1",
|
||||||
"socket.io": "~4.4.1",
|
"qs": "~6.10.4",
|
||||||
"socket.io-client": "~4.4.1",
|
"redbean-node": "~0.2.0",
|
||||||
|
"redis": "~4.5.1",
|
||||||
|
"socket.io": "~4.6.1",
|
||||||
|
"socket.io-client": "~4.6.1",
|
||||||
"socks-proxy-agent": "6.1.1",
|
"socks-proxy-agent": "6.1.1",
|
||||||
"tar": "^6.1.11",
|
"tar": "~6.1.11",
|
||||||
"tcp-ping": "~0.1.1",
|
"tcp-ping": "~0.1.1",
|
||||||
"thirty-two": "~1.0.2"
|
"thirty-two": "~1.0.2"
|
||||||
},
|
},
|
||||||
@@ -123,48 +138,54 @@
|
|||||||
"@vitejs/plugin-legacy": "~2.1.0",
|
"@vitejs/plugin-legacy": "~2.1.0",
|
||||||
"@vitejs/plugin-vue": "~3.1.0",
|
"@vitejs/plugin-vue": "~3.1.0",
|
||||||
"@vue/compiler-sfc": "~3.2.36",
|
"@vue/compiler-sfc": "~3.2.36",
|
||||||
|
"@vuepic/vue-datepicker": "~3.4.8",
|
||||||
"aedes": "^0.46.3",
|
"aedes": "^0.46.3",
|
||||||
"babel-plugin-rewire": "~1.2.0",
|
"babel-plugin-rewire": "~1.2.0",
|
||||||
"bootstrap": "5.1.3",
|
"bootstrap": "5.1.3",
|
||||||
"chart.js": "~3.6.2",
|
"chart.js": "~4.2.1",
|
||||||
"chartjs-adapter-dayjs": "~1.0.0",
|
"chartjs-adapter-dayjs-4": "~1.0.4",
|
||||||
"concurrently": "^7.1.0",
|
"concurrently": "^7.1.0",
|
||||||
"core-js": "~3.18.3",
|
"core-js": "~3.26.1",
|
||||||
|
"cronstrue": "~2.24.0",
|
||||||
"cross-env": "~7.0.3",
|
"cross-env": "~7.0.3",
|
||||||
"cypress": "^10.1.0",
|
"cypress": "^10.1.0",
|
||||||
"delay": "^5.0.0",
|
"delay": "^5.0.0",
|
||||||
"dns2": "~2.0.1",
|
"dns2": "~2.0.1",
|
||||||
|
"dompurify": "~2.4.3",
|
||||||
"eslint": "~8.14.0",
|
"eslint": "~8.14.0",
|
||||||
"eslint-plugin-vue": "~8.7.1",
|
"eslint-plugin-vue": "~8.7.1",
|
||||||
"favico.js": "^0.3.10",
|
"favico.js": "~0.3.10",
|
||||||
"jest": "~27.2.5",
|
"jest": "~27.2.5",
|
||||||
|
"marked": "~4.2.5",
|
||||||
|
"node-ssh": "~13.0.1",
|
||||||
"postcss-html": "~1.5.0",
|
"postcss-html": "~1.5.0",
|
||||||
"postcss-rtlcss": "~3.7.2",
|
"postcss-rtlcss": "~3.7.2",
|
||||||
"postcss-scss": "~4.0.4",
|
"postcss-scss": "~4.0.4",
|
||||||
"prismjs": "^1.27.0",
|
"prismjs": "~1.29.0",
|
||||||
"qrcode": "~1.5.0",
|
"qrcode": "~1.5.0",
|
||||||
"rollup-plugin-visualizer": "^5.6.0",
|
"rollup-plugin-visualizer": "^5.6.0",
|
||||||
"sass": "~1.42.1",
|
"sass": "~1.42.1",
|
||||||
"stylelint": "~14.7.1",
|
"stylelint": "~14.7.1",
|
||||||
"stylelint-config-standard": "~25.0.0",
|
"stylelint-config-standard": "~25.0.0",
|
||||||
"terser": "^5.15.0",
|
"terser": "~5.15.0",
|
||||||
"timezones-list": "~3.0.1",
|
"timezones-list": "~3.0.1",
|
||||||
"typescript": "~4.4.4",
|
"typescript": "~4.4.4",
|
||||||
"v-pagination-3": "~0.1.7",
|
"v-pagination-3": "~0.1.7",
|
||||||
"vite": "~3.1.0",
|
"vite": "~3.2.7",
|
||||||
"vite-plugin-compression": "^0.5.1",
|
"vite-plugin-compression": "^0.5.1",
|
||||||
"vue": "next",
|
"vue": "~3.2.47",
|
||||||
"vue-chart-3": "3.0.9",
|
"vue-chartjs": "~5.2.0",
|
||||||
"vue-confirm-dialog": "~1.0.2",
|
"vue-confirm-dialog": "~1.0.2",
|
||||||
"vue-contenteditable": "~3.0.4",
|
"vue-contenteditable": "~3.0.4",
|
||||||
"vue-i18n": "~9.1.9",
|
"vue-i18n": "~9.2.2",
|
||||||
"vue-image-crop-upload": "~3.0.3",
|
"vue-image-crop-upload": "~3.0.3",
|
||||||
"vue-multiselect": "~3.0.0-alpha.2",
|
"vue-multiselect": "~3.0.0-alpha.2",
|
||||||
"vue-prism-editor": "^2.0.0-alpha.2",
|
"vue-prism-editor": "~2.0.0-alpha.2",
|
||||||
"vue-qrcode": "~1.0.0",
|
"vue-qrcode": "~1.0.0",
|
||||||
"vue-router": "~4.0.14",
|
"vue-router": "~4.0.14",
|
||||||
"vue-toastification": "~2.0.0-rc.5",
|
"vue-toastification": "~2.0.0-rc.5",
|
||||||
"vuedraggable": "~4.1.0",
|
"vuedraggable": "~4.1.0",
|
||||||
"wait-on": "^6.0.1"
|
"wait-on": "^6.0.1",
|
||||||
|
"whatwg-url": "~12.0.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -2,7 +2,9 @@ const basicAuth = require("express-basic-auth");
|
|||||||
const passwordHash = require("./password-hash");
|
const passwordHash = require("./password-hash");
|
||||||
const { R } = require("redbean-node");
|
const { R } = require("redbean-node");
|
||||||
const { setting } = require("./util-server");
|
const { setting } = require("./util-server");
|
||||||
const { loginRateLimiter } = require("./rate-limiter");
|
const { loginRateLimiter, apiRateLimiter } = require("./rate-limiter");
|
||||||
|
const { Settings } = require("./settings");
|
||||||
|
const dayjs = require("dayjs");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Login to web app
|
* Login to web app
|
||||||
@@ -34,8 +36,36 @@ exports.login = async function (username, password) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Callback for myAuthorizer
|
* Validate a provided API key
|
||||||
* @callback myAuthorizerCB
|
* @param {string} key API key to verify
|
||||||
|
*/
|
||||||
|
async function verifyAPIKey(key) {
|
||||||
|
if (typeof key !== "string") {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// uk prefix + key ID is before _
|
||||||
|
let index = key.substring(2, key.indexOf("_"));
|
||||||
|
let clear = key.substring(key.indexOf("_") + 1, key.length);
|
||||||
|
|
||||||
|
let hash = await R.findOne("api_key", " id=? ", [ index ]);
|
||||||
|
|
||||||
|
if (hash === null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let current = dayjs();
|
||||||
|
let expiry = dayjs(hash.expires);
|
||||||
|
if (expiry.diff(current) < 0 || !hash.active) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return hash && passwordHash.verify(clear, hash.key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback for basic auth authorizers
|
||||||
|
* @callback authCallback
|
||||||
* @param {any} err Any error encountered
|
* @param {any} err Any error encountered
|
||||||
* @param {boolean} authorized Is the client authorized?
|
* @param {boolean} authorized Is the client authorized?
|
||||||
*/
|
*/
|
||||||
@@ -44,9 +74,31 @@ exports.login = async function (username, password) {
|
|||||||
* Custom authorizer for express-basic-auth
|
* Custom authorizer for express-basic-auth
|
||||||
* @param {string} username
|
* @param {string} username
|
||||||
* @param {string} password
|
* @param {string} password
|
||||||
* @param {myAuthorizerCB} callback
|
* @param {authCallback} callback
|
||||||
*/
|
*/
|
||||||
function myAuthorizer(username, password, callback) {
|
function apiAuthorizer(username, password, callback) {
|
||||||
|
// API Rate Limit
|
||||||
|
apiRateLimiter.pass(null, 0).then((pass) => {
|
||||||
|
if (pass) {
|
||||||
|
verifyAPIKey(password).then((valid) => {
|
||||||
|
callback(null, valid);
|
||||||
|
// Only allow a set number of api requests per minute
|
||||||
|
// (currently set to 60)
|
||||||
|
apiRateLimiter.removeTokens(1);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
callback(null, false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom authorizer for express-basic-auth
|
||||||
|
* @param {string} username
|
||||||
|
* @param {string} password
|
||||||
|
* @param {authCallback} callback
|
||||||
|
*/
|
||||||
|
function userAuthorizer(username, password, callback) {
|
||||||
// Login Rate Limit
|
// Login Rate Limit
|
||||||
loginRateLimiter.pass(null, 0).then((pass) => {
|
loginRateLimiter.pass(null, 0).then((pass) => {
|
||||||
if (pass) {
|
if (pass) {
|
||||||
@@ -63,9 +115,15 @@ function myAuthorizer(username, password, callback) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use basic auth if auth is not disabled
|
||||||
|
* @param {express.Request} req Express request object
|
||||||
|
* @param {express.Response} res Express response object
|
||||||
|
* @param {express.NextFunction} next
|
||||||
|
*/
|
||||||
exports.basicAuth = async function (req, res, next) {
|
exports.basicAuth = async function (req, res, next) {
|
||||||
const middleware = basicAuth({
|
const middleware = basicAuth({
|
||||||
authorizer: myAuthorizer,
|
authorizer: userAuthorizer,
|
||||||
authorizeAsync: true,
|
authorizeAsync: true,
|
||||||
challenge: true,
|
challenge: true,
|
||||||
});
|
});
|
||||||
@@ -78,3 +136,32 @@ exports.basicAuth = async function (req, res, next) {
|
|||||||
next();
|
next();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use use API Key if API keys enabled, else use basic auth
|
||||||
|
* @param {express.Request} req Express request object
|
||||||
|
* @param {express.Response} res Express response object
|
||||||
|
* @param {express.NextFunction} next
|
||||||
|
*/
|
||||||
|
exports.apiAuth = async function (req, res, next) {
|
||||||
|
if (!await Settings.get("disableAuth")) {
|
||||||
|
let usingAPIKeys = await Settings.get("apiKeysEnabled");
|
||||||
|
let middleware;
|
||||||
|
if (usingAPIKeys) {
|
||||||
|
middleware = basicAuth({
|
||||||
|
authorizer: apiAuthorizer,
|
||||||
|
authorizeAsync: true,
|
||||||
|
challenge: true,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
middleware = basicAuth({
|
||||||
|
authorizer: userAuthorizer,
|
||||||
|
authorizeAsync: true,
|
||||||
|
challenge: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
middleware(req, res, next);
|
||||||
|
} else {
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
@@ -1,6 +1,8 @@
|
|||||||
const https = require("https");
|
const https = require("https");
|
||||||
const http = require("http");
|
const http = require("http");
|
||||||
const CacheableLookup = require("cacheable-lookup");
|
const CacheableLookup = require("cacheable-lookup");
|
||||||
|
const { Settings } = require("./settings");
|
||||||
|
const { log } = require("../src/util");
|
||||||
|
|
||||||
class CacheableDnsHttpAgent {
|
class CacheableDnsHttpAgent {
|
||||||
|
|
||||||
@@ -9,14 +11,36 @@ class CacheableDnsHttpAgent {
|
|||||||
static httpAgentList = {};
|
static httpAgentList = {};
|
||||||
static httpsAgentList = {};
|
static httpsAgentList = {};
|
||||||
|
|
||||||
|
static enable = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register cacheable to global agents
|
* Register/Disable cacheable to global agents
|
||||||
*/
|
*/
|
||||||
static registerGlobalAgent() {
|
static async update() {
|
||||||
this.cacheable.install(http.globalAgent);
|
log.debug("CacheableDnsHttpAgent", "update");
|
||||||
this.cacheable.install(https.globalAgent);
|
let isEnable = await Settings.get("dnsCache");
|
||||||
|
|
||||||
|
if (isEnable !== this.enable) {
|
||||||
|
log.debug("CacheableDnsHttpAgent", "value changed");
|
||||||
|
|
||||||
|
if (isEnable) {
|
||||||
|
log.debug("CacheableDnsHttpAgent", "enable");
|
||||||
|
this.cacheable.install(http.globalAgent);
|
||||||
|
this.cacheable.install(https.globalAgent);
|
||||||
|
} else {
|
||||||
|
log.debug("CacheableDnsHttpAgent", "disable");
|
||||||
|
this.cacheable.uninstall(http.globalAgent);
|
||||||
|
this.cacheable.uninstall(https.globalAgent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.enable = isEnable;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attach cacheable to HTTP agent
|
||||||
|
* @param {http.Agent} agent Agent to install
|
||||||
|
*/
|
||||||
static install(agent) {
|
static install(agent) {
|
||||||
this.cacheable.install(agent);
|
this.cacheable.install(agent);
|
||||||
}
|
}
|
||||||
@@ -26,6 +50,10 @@ class CacheableDnsHttpAgent {
|
|||||||
* @return {https.Agent}
|
* @return {https.Agent}
|
||||||
*/
|
*/
|
||||||
static getHttpsAgent(agentOptions) {
|
static getHttpsAgent(agentOptions) {
|
||||||
|
if (!this.enable) {
|
||||||
|
return new https.Agent(agentOptions);
|
||||||
|
}
|
||||||
|
|
||||||
let key = JSON.stringify(agentOptions);
|
let key = JSON.stringify(agentOptions);
|
||||||
if (!(key in this.httpsAgentList)) {
|
if (!(key in this.httpsAgentList)) {
|
||||||
this.httpsAgentList[key] = new https.Agent(agentOptions);
|
this.httpsAgentList[key] = new https.Agent(agentOptions);
|
||||||
@@ -39,6 +67,10 @@ class CacheableDnsHttpAgent {
|
|||||||
* @return {https.Agents}
|
* @return {https.Agents}
|
||||||
*/
|
*/
|
||||||
static getHttpAgent(agentOptions) {
|
static getHttpAgent(agentOptions) {
|
||||||
|
if (!this.enable) {
|
||||||
|
return new http.Agent(agentOptions);
|
||||||
|
}
|
||||||
|
|
||||||
let key = JSON.stringify(agentOptions);
|
let key = JSON.stringify(agentOptions);
|
||||||
if (!(key in this.httpAgentList)) {
|
if (!(key in this.httpAgentList)) {
|
||||||
this.httpAgentList[key] = new http.Agent(agentOptions);
|
this.httpAgentList[key] = new http.Agent(agentOptions);
|
||||||
|
@@ -25,7 +25,7 @@ exports.startInterval = () => {
|
|||||||
let checkBeta = await setting("checkBeta");
|
let checkBeta = await setting("checkBeta");
|
||||||
|
|
||||||
if (checkBeta && res.data.beta) {
|
if (checkBeta && res.data.beta) {
|
||||||
if (compareVersions.compare(res.data.beta, res.data.beta, ">")) {
|
if (compareVersions.compare(res.data.beta, res.data.slow, ">")) {
|
||||||
exports.latestVersion = res.data.beta;
|
exports.latestVersion = res.data.beta;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@@ -4,7 +4,8 @@
|
|||||||
const { TimeLogger } = require("../src/util");
|
const { TimeLogger } = require("../src/util");
|
||||||
const { R } = require("redbean-node");
|
const { R } = require("redbean-node");
|
||||||
const { UptimeKumaServer } = require("./uptime-kuma-server");
|
const { UptimeKumaServer } = require("./uptime-kuma-server");
|
||||||
const io = UptimeKumaServer.getInstance().io;
|
const server = UptimeKumaServer.getInstance();
|
||||||
|
const io = server.io;
|
||||||
const { setting } = require("./util-server");
|
const { setting } = require("./util-server");
|
||||||
const checkVersion = require("./check-version");
|
const checkVersion = require("./check-version");
|
||||||
|
|
||||||
@@ -112,6 +113,31 @@ async function sendProxyList(socket) {
|
|||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emit API key list to client
|
||||||
|
* @param {Socket} socket Socket.io socket instance
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
async function sendAPIKeyList(socket) {
|
||||||
|
const timeLogger = new TimeLogger();
|
||||||
|
|
||||||
|
let result = [];
|
||||||
|
const list = await R.find(
|
||||||
|
"api_key",
|
||||||
|
"user_id=?",
|
||||||
|
[ socket.userID ],
|
||||||
|
);
|
||||||
|
|
||||||
|
for (let bean of list) {
|
||||||
|
result.push(bean.toPublicJSON());
|
||||||
|
}
|
||||||
|
|
||||||
|
io.to(socket.userID).emit("apiKeyList", result);
|
||||||
|
timeLogger.print("Sent API Key List");
|
||||||
|
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Emits the version information to the client.
|
* Emits the version information to the client.
|
||||||
* @param {Socket} socket Socket.io socket instance
|
* @param {Socket} socket Socket.io socket instance
|
||||||
@@ -121,7 +147,9 @@ async function sendInfo(socket) {
|
|||||||
socket.emit("info", {
|
socket.emit("info", {
|
||||||
version: checkVersion.version,
|
version: checkVersion.version,
|
||||||
latestVersion: checkVersion.latestVersion,
|
latestVersion: checkVersion.latestVersion,
|
||||||
primaryBaseURL: await setting("primaryBaseURL")
|
primaryBaseURL: await setting("primaryBaseURL"),
|
||||||
|
serverTimezone: await server.getTimezone(),
|
||||||
|
serverTimezoneOffset: server.getTimezoneOffset(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -154,6 +182,7 @@ module.exports = {
|
|||||||
sendImportantHeartbeatList,
|
sendImportantHeartbeatList,
|
||||||
sendHeartbeatList,
|
sendHeartbeatList,
|
||||||
sendProxyList,
|
sendProxyList,
|
||||||
|
sendAPIKeyList,
|
||||||
sendInfo,
|
sendInfo,
|
||||||
sendDockerHostList
|
sendDockerHostList
|
||||||
};
|
};
|
||||||
|
@@ -4,13 +4,21 @@ const demoMode = args["demo"] || false;
|
|||||||
const badgeConstants = {
|
const badgeConstants = {
|
||||||
naColor: "#999",
|
naColor: "#999",
|
||||||
defaultUpColor: "#66c20a",
|
defaultUpColor: "#66c20a",
|
||||||
|
defaultWarnColor: "#eed202",
|
||||||
defaultDownColor: "#c2290a",
|
defaultDownColor: "#c2290a",
|
||||||
|
defaultPendingColor: "#f8a306",
|
||||||
|
defaultMaintenanceColor: "#1747f5",
|
||||||
defaultPingColor: "blue", // as defined by badge-maker / shields.io
|
defaultPingColor: "blue", // as defined by badge-maker / shields.io
|
||||||
defaultStyle: "flat",
|
defaultStyle: "flat",
|
||||||
defaultPingValueSuffix: "ms",
|
defaultPingValueSuffix: "ms",
|
||||||
defaultPingLabelSuffix: "h",
|
defaultPingLabelSuffix: "h",
|
||||||
defaultUptimeValueSuffix: "%",
|
defaultUptimeValueSuffix: "%",
|
||||||
defaultUptimeLabelSuffix: "h",
|
defaultUptimeLabelSuffix: "h",
|
||||||
|
defaultCertExpValueSuffix: " days",
|
||||||
|
defaultCertExpLabelSuffix: "h",
|
||||||
|
// Values Come From Default Notification Times
|
||||||
|
defaultCertExpireWarnDays: "14",
|
||||||
|
defaultCertExpireDownDays: "7"
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
@@ -2,8 +2,8 @@ const fs = require("fs");
|
|||||||
const { R } = require("redbean-node");
|
const { R } = require("redbean-node");
|
||||||
const { setSetting, setting } = require("./util-server");
|
const { setSetting, setting } = require("./util-server");
|
||||||
const { log, sleep } = require("../src/util");
|
const { log, sleep } = require("../src/util");
|
||||||
const dayjs = require("dayjs");
|
|
||||||
const knex = require("knex");
|
const knex = require("knex");
|
||||||
|
const { PluginsManager } = require("./plugins-manager");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Database & App Data Folder
|
* Database & App Data Folder
|
||||||
@@ -29,11 +29,6 @@ class Database {
|
|||||||
*/
|
*/
|
||||||
static patched = false;
|
static patched = false;
|
||||||
|
|
||||||
/**
|
|
||||||
* For Backup only
|
|
||||||
*/
|
|
||||||
static backupPath = null;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add patch filename in key
|
* Add patch filename in key
|
||||||
* Values:
|
* Values:
|
||||||
@@ -62,8 +57,19 @@ class Database {
|
|||||||
"patch-add-clickable-status-page-link.sql": true,
|
"patch-add-clickable-status-page-link.sql": true,
|
||||||
"patch-add-sqlserver-monitor.sql": true,
|
"patch-add-sqlserver-monitor.sql": true,
|
||||||
"patch-add-other-auth.sql": { parents: [ "patch-monitor-basic-auth.sql" ] },
|
"patch-add-other-auth.sql": { parents: [ "patch-monitor-basic-auth.sql" ] },
|
||||||
|
"patch-grpc-monitor.sql": true,
|
||||||
"patch-add-radius-monitor.sql": true,
|
"patch-add-radius-monitor.sql": true,
|
||||||
"patch-monitor-add-resend-interval.sql": true,
|
"patch-monitor-add-resend-interval.sql": true,
|
||||||
|
"patch-ping-packet-size.sql": true,
|
||||||
|
"patch-maintenance-table2.sql": true,
|
||||||
|
"patch-add-gamedig-monitor.sql": true,
|
||||||
|
"patch-add-google-analytics-status-page-tag.sql": true,
|
||||||
|
"patch-http-body-encoding.sql": true,
|
||||||
|
"patch-add-description-monitor.sql": true,
|
||||||
|
"patch-api-key-table.sql": true,
|
||||||
|
"patch-monitor-tls.sql": true,
|
||||||
|
"patch-maintenance-cron.sql": true,
|
||||||
|
"patch-add-parent-monitor.sql": true,
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -81,6 +87,13 @@ class Database {
|
|||||||
static init(args) {
|
static init(args) {
|
||||||
// Data Directory (must be end with "/")
|
// Data Directory (must be end with "/")
|
||||||
Database.dataDir = process.env.DATA_DIR || args["data-dir"] || "./data/";
|
Database.dataDir = process.env.DATA_DIR || args["data-dir"] || "./data/";
|
||||||
|
|
||||||
|
// Plugin feature is working only if the dataDir = "./data";
|
||||||
|
if (Database.dataDir !== "./data/") {
|
||||||
|
log.warn("PLUGIN", "Warning: In order to enable plugin feature, you need to use the default data directory: ./data/");
|
||||||
|
PluginsManager.disable = true;
|
||||||
|
}
|
||||||
|
|
||||||
Database.path = Database.dataDir + "kuma.db";
|
Database.path = Database.dataDir + "kuma.db";
|
||||||
if (! fs.existsSync(Database.dataDir)) {
|
if (! fs.existsSync(Database.dataDir)) {
|
||||||
fs.mkdirSync(Database.dataDir, { recursive: true });
|
fs.mkdirSync(Database.dataDir, { recursive: true });
|
||||||
@@ -150,9 +163,6 @@ class Database {
|
|||||||
await R.exec("PRAGMA cache_size = -12000");
|
await R.exec("PRAGMA cache_size = -12000");
|
||||||
await R.exec("PRAGMA auto_vacuum = FULL");
|
await R.exec("PRAGMA auto_vacuum = FULL");
|
||||||
|
|
||||||
// Avoid error "SQLITE_BUSY: database is locked" by allowing SQLITE to wait up to 5 seconds to do a write
|
|
||||||
await R.exec("PRAGMA busy_timeout = 5000");
|
|
||||||
|
|
||||||
// This ensures that an operating system crash or power failure will not corrupt the database.
|
// This ensures that an operating system crash or power failure will not corrupt the database.
|
||||||
// FULL synchronous is very safe, but it is also slower.
|
// FULL synchronous is very safe, but it is also slower.
|
||||||
// Read more: https://sqlite.org/pragma.html#pragma_synchronous
|
// Read more: https://sqlite.org/pragma.html#pragma_synchronous
|
||||||
@@ -184,15 +194,7 @@ class Database {
|
|||||||
} else {
|
} else {
|
||||||
log.info("db", "Database patch is needed");
|
log.info("db", "Database patch is needed");
|
||||||
|
|
||||||
try {
|
// Try catch anything here
|
||||||
this.backup(version);
|
|
||||||
} catch (e) {
|
|
||||||
log.error("db", e);
|
|
||||||
log.error("db", "Unable to create a backup before patching the database. Please make sure you have enough space and permission.");
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try catch anything here, if gone wrong, restore the backup
|
|
||||||
try {
|
try {
|
||||||
for (let i = version + 1; i <= this.latestVersion; i++) {
|
for (let i = version + 1; i <= this.latestVersion; i++) {
|
||||||
const sqlFile = `./db/patch${i}.sql`;
|
const sqlFile = `./db/patch${i}.sql`;
|
||||||
@@ -208,7 +210,6 @@ class Database {
|
|||||||
log.error("db", "Start Uptime-Kuma failed due to issue patching the database");
|
log.error("db", "Start Uptime-Kuma failed due to issue patching the database");
|
||||||
log.error("db", "Please submit a bug report if you still encounter the problem after restart: https://github.com/louislam/uptime-kuma/issues");
|
log.error("db", "Please submit a bug report if you still encounter the problem after restart: https://github.com/louislam/uptime-kuma/issues");
|
||||||
|
|
||||||
this.restore();
|
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -250,8 +251,6 @@ class Database {
|
|||||||
log.error("db", "Start Uptime-Kuma failed due to issue patching the database");
|
log.error("db", "Start Uptime-Kuma failed due to issue patching the database");
|
||||||
log.error("db", "Please submit the bug report if you still encounter the problem after restart: https://github.com/louislam/uptime-kuma/issues");
|
log.error("db", "Please submit the bug report if you still encounter the problem after restart: https://github.com/louislam/uptime-kuma/issues");
|
||||||
|
|
||||||
this.restore();
|
|
||||||
|
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -353,8 +352,6 @@ class Database {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.backup(dayjs().format("YYYYMMDDHHmmss"));
|
|
||||||
|
|
||||||
log.info("db", sqlFilename + " is patching");
|
log.info("db", sqlFilename + " is patching");
|
||||||
this.patched = true;
|
this.patched = true;
|
||||||
await this.importSQLFile("./db/" + sqlFilename);
|
await this.importSQLFile("./db/" + sqlFilename);
|
||||||
@@ -420,6 +417,9 @@ class Database {
|
|||||||
|
|
||||||
log.info("db", "Closing the database");
|
log.info("db", "Closing the database");
|
||||||
|
|
||||||
|
// Flush WAL to main database
|
||||||
|
await R.exec("PRAGMA wal_checkpoint(TRUNCATE)");
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
Database.noReject = true;
|
Database.noReject = true;
|
||||||
await R.close();
|
await R.close();
|
||||||
@@ -436,90 +436,6 @@ class Database {
|
|||||||
process.removeListener("unhandledRejection", listener);
|
process.removeListener("unhandledRejection", listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* One backup one time in this process.
|
|
||||||
* Reset this.backupPath if you want to backup again
|
|
||||||
* @param {string} version Version code of backup
|
|
||||||
*/
|
|
||||||
static backup(version) {
|
|
||||||
if (! this.backupPath) {
|
|
||||||
log.info("db", "Backing up the database");
|
|
||||||
this.backupPath = this.dataDir + "kuma.db.bak" + version;
|
|
||||||
fs.copyFileSync(Database.path, this.backupPath);
|
|
||||||
|
|
||||||
const shmPath = Database.path + "-shm";
|
|
||||||
if (fs.existsSync(shmPath)) {
|
|
||||||
this.backupShmPath = shmPath + ".bak" + version;
|
|
||||||
fs.copyFileSync(shmPath, this.backupShmPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
const walPath = Database.path + "-wal";
|
|
||||||
if (fs.existsSync(walPath)) {
|
|
||||||
this.backupWalPath = walPath + ".bak" + version;
|
|
||||||
fs.copyFileSync(walPath, this.backupWalPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Double confirm if all files actually backup
|
|
||||||
if (!fs.existsSync(this.backupPath)) {
|
|
||||||
throw new Error("Backup failed! " + this.backupPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fs.existsSync(shmPath)) {
|
|
||||||
if (!fs.existsSync(this.backupShmPath)) {
|
|
||||||
throw new Error("Backup failed! " + this.backupShmPath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fs.existsSync(walPath)) {
|
|
||||||
if (!fs.existsSync(this.backupWalPath)) {
|
|
||||||
throw new Error("Backup failed! " + this.backupWalPath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Restore from most recent backup */
|
|
||||||
static restore() {
|
|
||||||
if (this.backupPath) {
|
|
||||||
log.error("db", "Patching the database failed!!! Restoring the backup");
|
|
||||||
|
|
||||||
const shmPath = Database.path + "-shm";
|
|
||||||
const walPath = Database.path + "-wal";
|
|
||||||
|
|
||||||
// Delete patch failed db
|
|
||||||
try {
|
|
||||||
if (fs.existsSync(Database.path)) {
|
|
||||||
fs.unlinkSync(Database.path);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fs.existsSync(shmPath)) {
|
|
||||||
fs.unlinkSync(shmPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fs.existsSync(walPath)) {
|
|
||||||
fs.unlinkSync(walPath);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
log.error("db", "Restore failed; you may need to restore the backup manually");
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Restore backup
|
|
||||||
fs.copyFileSync(this.backupPath, Database.path);
|
|
||||||
|
|
||||||
if (this.backupShmPath) {
|
|
||||||
fs.copyFileSync(this.backupShmPath, shmPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.backupWalPath) {
|
|
||||||
fs.copyFileSync(this.backupWalPath, walPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
log.info("db", "Nothing to restore");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Get the size of the database */
|
/** Get the size of the database */
|
||||||
static getSize() {
|
static getSize() {
|
||||||
log.debug("db", "Database.getSize()");
|
log.debug("db", "Database.getSize()");
|
||||||
|
24
server/git.js
Normal file
24
server/git.js
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
const childProcess = require("child_process");
|
||||||
|
|
||||||
|
class Git {
|
||||||
|
|
||||||
|
static clone(repoURL, cwd, targetDir = ".") {
|
||||||
|
let result = childProcess.spawnSync("git", [
|
||||||
|
"clone",
|
||||||
|
repoURL,
|
||||||
|
targetDir,
|
||||||
|
], {
|
||||||
|
cwd: cwd,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result.status !== 0) {
|
||||||
|
throw new Error(result.stderr.toString("utf-8"));
|
||||||
|
} else {
|
||||||
|
return result.stdout.toString("utf-8") + result.stderr.toString("utf-8");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
Git,
|
||||||
|
};
|
24
server/google-analytics.js
Normal file
24
server/google-analytics.js
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
const jsesc = require("jsesc");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a string that represents the javascript that is required to insert the Google Analytics scripts
|
||||||
|
* into a webpage.
|
||||||
|
* @param tagId Google UA/G/AW/DC Property ID to use with the Google Analytics script.
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
function getGoogleAnalyticsScript(tagId) {
|
||||||
|
let escapedTagId = jsesc(tagId, { isScriptContext: true });
|
||||||
|
|
||||||
|
if (escapedTagId) {
|
||||||
|
escapedTagId = escapedTagId.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
return `
|
||||||
|
<script async src="https://www.googletagmanager.com/gtag/js?id=${escapedTagId}"></script>
|
||||||
|
<script>window.dataLayer = window.dataLayer || []; function gtag(){dataLayer.push(arguments);} gtag('js', new Date());gtag('config', '${escapedTagId}'); </script>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
getGoogleAnalyticsScript,
|
||||||
|
};
|
@@ -1,40 +1,44 @@
|
|||||||
const path = require("path");
|
const { UptimeKumaServer } = require("./uptime-kuma-server");
|
||||||
const Bree = require("bree");
|
const { clearOldData } = require("./jobs/clear-old-data");
|
||||||
const { SHARE_ENV } = require("worker_threads");
|
const Cron = require("croner");
|
||||||
const { log } = require("../src/util");
|
|
||||||
let bree;
|
|
||||||
const jobs = [
|
const jobs = [
|
||||||
{
|
{
|
||||||
name: "clear-old-data",
|
name: "clear-old-data",
|
||||||
interval: "at 03:14",
|
interval: "14 03 * * *",
|
||||||
|
jobFunc: clearOldData,
|
||||||
|
croner: null,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize background jobs
|
* Initialize background jobs
|
||||||
* @param {Object} args Arguments to pass to workers
|
* @returns {Promise<void>}
|
||||||
* @returns {Bree}
|
|
||||||
*/
|
*/
|
||||||
const initBackgroundJobs = function (args) {
|
const initBackgroundJobs = async function () {
|
||||||
bree = new Bree({
|
const timezone = await UptimeKumaServer.getInstance().getTimezone();
|
||||||
root: path.resolve("server", "jobs"),
|
|
||||||
jobs,
|
for (const job of jobs) {
|
||||||
worker: {
|
const cornerJob = new Cron(
|
||||||
env: SHARE_ENV,
|
job.interval,
|
||||||
workerData: args,
|
{
|
||||||
},
|
name: job.name,
|
||||||
workerMessageHandler: (message) => {
|
timezone,
|
||||||
log.info("jobs", message);
|
},
|
||||||
}
|
job.jobFunc,
|
||||||
});
|
);
|
||||||
|
job.croner = cornerJob;
|
||||||
|
}
|
||||||
|
|
||||||
bree.start();
|
|
||||||
return bree;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** Stop all background jobs if running */
|
||||||
const stopBackgroundJobs = function () {
|
const stopBackgroundJobs = function () {
|
||||||
if (bree) {
|
for (const job of jobs) {
|
||||||
bree.stop();
|
if (job.croner) {
|
||||||
|
job.croner.stop();
|
||||||
|
job.croner = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -1,12 +1,15 @@
|
|||||||
const { log, exit, connectDb } = require("./util-worker");
|
|
||||||
const { R } = require("redbean-node");
|
const { R } = require("redbean-node");
|
||||||
|
const { log } = require("../../src/util");
|
||||||
const { setSetting, setting } = require("../util-server");
|
const { setSetting, setting } = require("../util-server");
|
||||||
|
|
||||||
const DEFAULT_KEEP_PERIOD = 180;
|
const DEFAULT_KEEP_PERIOD = 180;
|
||||||
|
|
||||||
(async () => {
|
/**
|
||||||
await connectDb();
|
* Clears old data from the heartbeat table of the database.
|
||||||
|
* @return {Promise<void>} A promise that resolves when the data has been cleared.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const clearOldData = async () => {
|
||||||
let period = await setting("keepDataPeriodDays");
|
let period = await setting("keepDataPeriodDays");
|
||||||
|
|
||||||
// Set Default Period
|
// Set Default Period
|
||||||
@@ -20,21 +23,28 @@ const DEFAULT_KEEP_PERIOD = 180;
|
|||||||
try {
|
try {
|
||||||
parsedPeriod = parseInt(period);
|
parsedPeriod = parseInt(period);
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
log("Failed to parse setting, resetting to default..");
|
log.warn("clearOldData", "Failed to parse setting, resetting to default..");
|
||||||
await setSetting("keepDataPeriodDays", DEFAULT_KEEP_PERIOD, "general");
|
await setSetting("keepDataPeriodDays", DEFAULT_KEEP_PERIOD, "general");
|
||||||
parsedPeriod = DEFAULT_KEEP_PERIOD;
|
parsedPeriod = DEFAULT_KEEP_PERIOD;
|
||||||
}
|
}
|
||||||
|
|
||||||
log(`Clearing Data older than ${parsedPeriod} days...`);
|
if (parsedPeriod < 1) {
|
||||||
|
log.info("clearOldData", `Data deletion has been disabled as period is less than 1. Period is ${parsedPeriod} days.`);
|
||||||
|
} else {
|
||||||
|
|
||||||
try {
|
log.debug("clearOldData", `Clearing Data older than ${parsedPeriod} days...`);
|
||||||
await R.exec(
|
|
||||||
"DELETE FROM heartbeat WHERE time < DATETIME('now', '-' || ? || ' days') ",
|
try {
|
||||||
[ parsedPeriod ]
|
await R.exec(
|
||||||
);
|
"DELETE FROM heartbeat WHERE time < DATETIME('now', '-' || ? || ' days') ",
|
||||||
} catch (e) {
|
[ parsedPeriod ]
|
||||||
log(`Failed to clear old data: ${e.message}`);
|
);
|
||||||
|
} catch (e) {
|
||||||
|
log.error("clearOldData", `Failed to clear old data: ${e.message}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
exit();
|
module.exports = {
|
||||||
})();
|
clearOldData,
|
||||||
|
};
|
||||||
|
@@ -1,50 +0,0 @@
|
|||||||
const { parentPort, workerData } = require("worker_threads");
|
|
||||||
const Database = require("../database");
|
|
||||||
const path = require("path");
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Send message to parent process for logging
|
|
||||||
* since worker_thread does not have access to stdout, this is used
|
|
||||||
* instead of console.log()
|
|
||||||
* @param {any} any The message to log
|
|
||||||
*/
|
|
||||||
const log = function (any) {
|
|
||||||
if (parentPort) {
|
|
||||||
parentPort.postMessage(any);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Exit the worker process
|
|
||||||
* @param {number} error The status code to exit
|
|
||||||
*/
|
|
||||||
const exit = function (error) {
|
|
||||||
if (error && error !== 0) {
|
|
||||||
process.exit(error);
|
|
||||||
} else {
|
|
||||||
if (parentPort) {
|
|
||||||
parentPort.postMessage("done");
|
|
||||||
} else {
|
|
||||||
process.exit(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/** Connects to the database */
|
|
||||||
const connectDb = async function () {
|
|
||||||
const dbPath = path.join(
|
|
||||||
process.env.DATA_DIR || workerData["data-dir"] || "./data/"
|
|
||||||
);
|
|
||||||
|
|
||||||
Database.init({
|
|
||||||
"data-dir": dbPath,
|
|
||||||
});
|
|
||||||
|
|
||||||
await Database.connect();
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
log,
|
|
||||||
exit,
|
|
||||||
connectDb,
|
|
||||||
};
|
|
76
server/model/api_key.js
Normal file
76
server/model/api_key.js
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
const { BeanModel } = require("redbean-node/dist/bean-model");
|
||||||
|
const { R } = require("redbean-node");
|
||||||
|
const dayjs = require("dayjs");
|
||||||
|
|
||||||
|
class APIKey extends BeanModel {
|
||||||
|
/**
|
||||||
|
* Get the current status of this API key
|
||||||
|
* @returns {string} active, inactive or expired
|
||||||
|
*/
|
||||||
|
getStatus() {
|
||||||
|
let current = dayjs();
|
||||||
|
let expiry = dayjs(this.expires);
|
||||||
|
if (expiry.diff(current) < 0) {
|
||||||
|
return "expired";
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.active ? "active" : "inactive";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an object that ready to parse to JSON
|
||||||
|
* @returns {Object}
|
||||||
|
*/
|
||||||
|
toJSON() {
|
||||||
|
return {
|
||||||
|
id: this.id,
|
||||||
|
key: this.key,
|
||||||
|
name: this.name,
|
||||||
|
userID: this.user_id,
|
||||||
|
createdDate: this.created_date,
|
||||||
|
active: this.active,
|
||||||
|
expires: this.expires,
|
||||||
|
status: this.getStatus(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an object that ready to parse to JSON with sensitive fields
|
||||||
|
* removed
|
||||||
|
* @returns {Object}
|
||||||
|
*/
|
||||||
|
toPublicJSON() {
|
||||||
|
return {
|
||||||
|
id: this.id,
|
||||||
|
name: this.name,
|
||||||
|
userID: this.user_id,
|
||||||
|
createdDate: this.created_date,
|
||||||
|
active: this.active,
|
||||||
|
expires: this.expires,
|
||||||
|
status: this.getStatus(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new API Key and store it in the database
|
||||||
|
* @param {Object} key Object sent by client
|
||||||
|
* @param {int} userID ID of socket user
|
||||||
|
* @returns {Promise<bean>}
|
||||||
|
*/
|
||||||
|
static async save(key, userID) {
|
||||||
|
let bean;
|
||||||
|
bean = R.dispense("api_key");
|
||||||
|
|
||||||
|
bean.key = key.key;
|
||||||
|
bean.name = key.name;
|
||||||
|
bean.user_id = userID;
|
||||||
|
bean.active = key.active;
|
||||||
|
bean.expires = key.expires;
|
||||||
|
|
||||||
|
await R.store(bean);
|
||||||
|
|
||||||
|
return bean;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = APIKey;
|
@@ -1,8 +1,3 @@
|
|||||||
const dayjs = require("dayjs");
|
|
||||||
const utc = require("dayjs/plugin/utc");
|
|
||||||
let timezone = require("dayjs/plugin/timezone");
|
|
||||||
dayjs.extend(utc);
|
|
||||||
dayjs.extend(timezone);
|
|
||||||
const { BeanModel } = require("redbean-node/dist/bean-model");
|
const { BeanModel } = require("redbean-node/dist/bean-model");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -10,6 +5,7 @@ const { BeanModel } = require("redbean-node/dist/bean-model");
|
|||||||
* 0 = DOWN
|
* 0 = DOWN
|
||||||
* 1 = UP
|
* 1 = UP
|
||||||
* 2 = PENDING
|
* 2 = PENDING
|
||||||
|
* 3 = MAINTENANCE
|
||||||
*/
|
*/
|
||||||
class Heartbeat extends BeanModel {
|
class Heartbeat extends BeanModel {
|
||||||
|
|
||||||
|
415
server/model/maintenance.js
Normal file
415
server/model/maintenance.js
Normal file
@@ -0,0 +1,415 @@
|
|||||||
|
const { BeanModel } = require("redbean-node/dist/bean-model");
|
||||||
|
const { parseTimeObject, parseTimeFromTimeObject, log } = require("../../src/util");
|
||||||
|
const { R } = require("redbean-node");
|
||||||
|
const dayjs = require("dayjs");
|
||||||
|
const Cron = require("croner");
|
||||||
|
const { UptimeKumaServer } = require("../uptime-kuma-server");
|
||||||
|
const apicache = require("../modules/apicache");
|
||||||
|
|
||||||
|
class Maintenance extends BeanModel {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return an object that ready to parse to JSON for public
|
||||||
|
* Only show necessary data to public
|
||||||
|
* @returns {Object}
|
||||||
|
*/
|
||||||
|
async toPublicJSON() {
|
||||||
|
|
||||||
|
let dateRange = [];
|
||||||
|
if (this.start_date) {
|
||||||
|
dateRange.push(this.start_date);
|
||||||
|
} else {
|
||||||
|
dateRange.push(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.end_date) {
|
||||||
|
dateRange.push(this.end_date);
|
||||||
|
}
|
||||||
|
|
||||||
|
let timeRange = [];
|
||||||
|
let startTime = parseTimeObject(this.start_time);
|
||||||
|
timeRange.push(startTime);
|
||||||
|
let endTime = parseTimeObject(this.end_time);
|
||||||
|
timeRange.push(endTime);
|
||||||
|
|
||||||
|
let obj = {
|
||||||
|
id: this.id,
|
||||||
|
title: this.title,
|
||||||
|
description: this.description,
|
||||||
|
strategy: this.strategy,
|
||||||
|
intervalDay: this.interval_day,
|
||||||
|
active: !!this.active,
|
||||||
|
dateRange: dateRange,
|
||||||
|
timeRange: timeRange,
|
||||||
|
weekdays: (this.weekdays) ? JSON.parse(this.weekdays) : [],
|
||||||
|
daysOfMonth: (this.days_of_month) ? JSON.parse(this.days_of_month) : [],
|
||||||
|
timeslotList: [],
|
||||||
|
cron: this.cron,
|
||||||
|
duration: this.duration,
|
||||||
|
durationMinutes: parseInt(this.duration / 60),
|
||||||
|
timezone: await this.getTimezone(), // Only valid timezone
|
||||||
|
timezoneOption: this.timezone, // Mainly for dropdown menu, because there is a option "SAME_AS_SERVER"
|
||||||
|
timezoneOffset: await this.getTimezoneOffset(),
|
||||||
|
status: await this.getStatus(),
|
||||||
|
};
|
||||||
|
|
||||||
|
if (this.strategy === "manual") {
|
||||||
|
// Do nothing, no timeslots
|
||||||
|
} else if (this.strategy === "single") {
|
||||||
|
obj.timeslotList.push({
|
||||||
|
startDate: this.start_date,
|
||||||
|
endDate: this.end_date,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Should be cron or recurring here
|
||||||
|
if (this.beanMeta.job) {
|
||||||
|
let runningTimeslot = this.getRunningTimeslot();
|
||||||
|
|
||||||
|
if (runningTimeslot) {
|
||||||
|
obj.timeslotList.push(runningTimeslot);
|
||||||
|
}
|
||||||
|
|
||||||
|
let nextRunDate = this.beanMeta.job.nextRun();
|
||||||
|
if (nextRunDate) {
|
||||||
|
let startDateDayjs = dayjs(nextRunDate);
|
||||||
|
|
||||||
|
let startDate = startDateDayjs.toISOString();
|
||||||
|
let endDate = startDateDayjs.add(this.duration, "second").toISOString();
|
||||||
|
|
||||||
|
obj.timeslotList.push({
|
||||||
|
startDate,
|
||||||
|
endDate,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Array.isArray(obj.weekdays)) {
|
||||||
|
obj.weekdays = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Array.isArray(obj.daysOfMonth)) {
|
||||||
|
obj.daysOfMonth = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return an object that ready to parse to JSON
|
||||||
|
* @param {string} timezone If not specified, the timeRange will be in UTC
|
||||||
|
* @returns {Object}
|
||||||
|
*/
|
||||||
|
async toJSON(timezone = null) {
|
||||||
|
return this.toPublicJSON(timezone);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a list of weekdays that the maintenance is active for
|
||||||
|
* Monday=1, Tuesday=2 etc.
|
||||||
|
* @returns {number[]} Array of active weekdays
|
||||||
|
*/
|
||||||
|
getDayOfWeekList() {
|
||||||
|
log.debug("timeslot", "List: " + this.weekdays);
|
||||||
|
return JSON.parse(this.weekdays).sort(function (a, b) {
|
||||||
|
return a - b;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a list of days in month that maintenance is active for
|
||||||
|
* @returns {number[]|string[]} Array of active days in month
|
||||||
|
*/
|
||||||
|
getDayOfMonthList() {
|
||||||
|
return JSON.parse(this.days_of_month).sort(function (a, b) {
|
||||||
|
return a - b;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the duration of maintenance in seconds
|
||||||
|
* @returns {number} Duration of maintenance
|
||||||
|
*/
|
||||||
|
calcDuration() {
|
||||||
|
let duration = dayjs.utc(this.end_time, "HH:mm").diff(dayjs.utc(this.start_time, "HH:mm"), "second");
|
||||||
|
// Add 24hours if it is across day
|
||||||
|
if (duration < 0) {
|
||||||
|
duration += 24 * 3600;
|
||||||
|
}
|
||||||
|
return duration;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert data from socket to bean
|
||||||
|
* @param {Bean} bean Bean to fill in
|
||||||
|
* @param {Object} obj Data to fill bean with
|
||||||
|
* @returns {Bean} Filled bean
|
||||||
|
*/
|
||||||
|
static async jsonToBean(bean, obj) {
|
||||||
|
if (obj.id) {
|
||||||
|
bean.id = obj.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
bean.title = obj.title;
|
||||||
|
bean.description = obj.description;
|
||||||
|
bean.strategy = obj.strategy;
|
||||||
|
bean.interval_day = obj.intervalDay;
|
||||||
|
bean.timezone = obj.timezoneOption;
|
||||||
|
bean.active = obj.active;
|
||||||
|
|
||||||
|
if (obj.dateRange[0]) {
|
||||||
|
bean.start_date = obj.dateRange[0];
|
||||||
|
} else {
|
||||||
|
bean.start_date = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (obj.dateRange[1]) {
|
||||||
|
bean.end_date = obj.dateRange[1];
|
||||||
|
} else {
|
||||||
|
bean.end_date = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bean.strategy === "cron") {
|
||||||
|
bean.duration = obj.durationMinutes * 60;
|
||||||
|
bean.cron = obj.cron;
|
||||||
|
this.validateCron(bean.cron);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bean.strategy.startsWith("recurring-")) {
|
||||||
|
bean.start_time = parseTimeFromTimeObject(obj.timeRange[0]);
|
||||||
|
bean.end_time = parseTimeFromTimeObject(obj.timeRange[1]);
|
||||||
|
bean.weekdays = JSON.stringify(obj.weekdays);
|
||||||
|
bean.days_of_month = JSON.stringify(obj.daysOfMonth);
|
||||||
|
await bean.generateCron();
|
||||||
|
this.validateCron(bean.cron);
|
||||||
|
}
|
||||||
|
return bean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Throw error if cron is invalid
|
||||||
|
* @param cron
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
static async validateCron(cron) {
|
||||||
|
let job = new Cron(cron, () => {});
|
||||||
|
job.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run the cron
|
||||||
|
*/
|
||||||
|
async run(throwError = false) {
|
||||||
|
if (this.beanMeta.job) {
|
||||||
|
log.debug("maintenance", "Maintenance is already running, stop it first. id: " + this.id);
|
||||||
|
this.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
log.debug("maintenance", "Run maintenance id: " + this.id);
|
||||||
|
|
||||||
|
// 1.21.2 migration
|
||||||
|
if (!this.cron) {
|
||||||
|
await this.generateCron();
|
||||||
|
if (!this.timezone) {
|
||||||
|
this.timezone = "UTC";
|
||||||
|
}
|
||||||
|
if (this.cron) {
|
||||||
|
await R.store(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.strategy === "manual") {
|
||||||
|
// Do nothing, because it is controlled by the user
|
||||||
|
} else if (this.strategy === "single") {
|
||||||
|
this.beanMeta.job = new Cron(this.start_date, { timezone: await this.getTimezone() }, () => {
|
||||||
|
log.info("maintenance", "Maintenance id: " + this.id + " is under maintenance now");
|
||||||
|
UptimeKumaServer.getInstance().sendMaintenanceListByUserID(this.user_id);
|
||||||
|
apicache.clear();
|
||||||
|
});
|
||||||
|
} else if (this.cron != null) {
|
||||||
|
// Here should be cron or recurring
|
||||||
|
try {
|
||||||
|
this.beanMeta.status = "scheduled";
|
||||||
|
|
||||||
|
let startEvent = (customDuration = 0) => {
|
||||||
|
log.info("maintenance", "Maintenance id: " + this.id + " is under maintenance now");
|
||||||
|
|
||||||
|
this.beanMeta.status = "under-maintenance";
|
||||||
|
clearTimeout(this.beanMeta.durationTimeout);
|
||||||
|
|
||||||
|
// Check if duration is still in the window. If not, use the duration from the current time to the end of the window
|
||||||
|
let duration;
|
||||||
|
|
||||||
|
if (customDuration > 0) {
|
||||||
|
duration = customDuration;
|
||||||
|
} else if (this.end_date) {
|
||||||
|
let d = dayjs(this.end_date).diff(dayjs(), "second");
|
||||||
|
if (d < this.duration) {
|
||||||
|
duration = d * 1000;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
duration = this.duration * 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
UptimeKumaServer.getInstance().sendMaintenanceListByUserID(this.user_id);
|
||||||
|
|
||||||
|
this.beanMeta.durationTimeout = setTimeout(() => {
|
||||||
|
// End of maintenance for this timeslot
|
||||||
|
this.beanMeta.status = "scheduled";
|
||||||
|
UptimeKumaServer.getInstance().sendMaintenanceListByUserID(this.user_id);
|
||||||
|
}, duration);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create Cron
|
||||||
|
this.beanMeta.job = new Cron(this.cron, {
|
||||||
|
timezone: await this.getTimezone(),
|
||||||
|
}, startEvent);
|
||||||
|
|
||||||
|
// Continue if the maintenance is still in the window
|
||||||
|
let runningTimeslot = this.getRunningTimeslot();
|
||||||
|
let current = dayjs();
|
||||||
|
|
||||||
|
if (runningTimeslot) {
|
||||||
|
let duration = dayjs(runningTimeslot.endDate).diff(current, "second") * 1000;
|
||||||
|
log.debug("maintenance", "Maintenance id: " + this.id + " Remaining duration: " + duration + "ms");
|
||||||
|
startEvent(duration);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
log.error("maintenance", "Error in maintenance id: " + this.id);
|
||||||
|
log.error("maintenance", "Cron: " + this.cron);
|
||||||
|
log.error("maintenance", e);
|
||||||
|
|
||||||
|
if (throwError) {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
log.error("maintenance", "Maintenance id: " + this.id + " has no cron");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getRunningTimeslot() {
|
||||||
|
let start = dayjs(this.beanMeta.job.nextRun(dayjs().add(-this.duration, "second").toDate()));
|
||||||
|
let end = start.add(this.duration, "second");
|
||||||
|
let current = dayjs();
|
||||||
|
|
||||||
|
if (current.isAfter(start) && current.isBefore(end)) {
|
||||||
|
return {
|
||||||
|
startDate: start.toISOString(),
|
||||||
|
endDate: end.toISOString(),
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stop() {
|
||||||
|
if (this.beanMeta.job) {
|
||||||
|
this.beanMeta.job.stop();
|
||||||
|
delete this.beanMeta.job;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async isUnderMaintenance() {
|
||||||
|
return (await this.getStatus()) === "under-maintenance";
|
||||||
|
}
|
||||||
|
|
||||||
|
async getTimezone() {
|
||||||
|
if (!this.timezone || this.timezone === "SAME_AS_SERVER") {
|
||||||
|
return await UptimeKumaServer.getInstance().getTimezone();
|
||||||
|
}
|
||||||
|
return this.timezone;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getTimezoneOffset() {
|
||||||
|
return dayjs.tz(dayjs(), await this.getTimezone()).format("Z");
|
||||||
|
}
|
||||||
|
|
||||||
|
async getStatus() {
|
||||||
|
if (!this.active) {
|
||||||
|
return "inactive";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.strategy === "manual") {
|
||||||
|
return "under-maintenance";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the maintenance is started
|
||||||
|
if (this.start_date && dayjs().isBefore(dayjs.tz(this.start_date, await this.getTimezone()))) {
|
||||||
|
return "scheduled";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the maintenance is ended
|
||||||
|
if (this.end_date && dayjs().isAfter(dayjs.tz(this.end_date, await this.getTimezone()))) {
|
||||||
|
return "ended";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.strategy === "single") {
|
||||||
|
return "under-maintenance";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.beanMeta.status) {
|
||||||
|
return "unknown";
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.beanMeta.status;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate Cron for recurring maintenance
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
async generateCron() {
|
||||||
|
log.info("maintenance", "Generate cron for maintenance id: " + this.id);
|
||||||
|
|
||||||
|
if (this.strategy === "cron") {
|
||||||
|
// Do nothing for cron
|
||||||
|
} else if (!this.strategy.startsWith("recurring-")) {
|
||||||
|
this.cron = "";
|
||||||
|
} else if (this.strategy === "recurring-interval") {
|
||||||
|
let array = this.start_time.split(":");
|
||||||
|
let hour = parseInt(array[0]);
|
||||||
|
let minute = parseInt(array[1]);
|
||||||
|
this.cron = minute + " " + hour + " */" + this.interval_day + " * *";
|
||||||
|
this.duration = this.calcDuration();
|
||||||
|
log.debug("maintenance", "Cron: " + this.cron);
|
||||||
|
log.debug("maintenance", "Duration: " + this.duration);
|
||||||
|
} else if (this.strategy === "recurring-weekday") {
|
||||||
|
let list = this.getDayOfWeekList();
|
||||||
|
let array = this.start_time.split(":");
|
||||||
|
let hour = parseInt(array[0]);
|
||||||
|
let minute = parseInt(array[1]);
|
||||||
|
this.cron = minute + " " + hour + " * * " + list.join(",");
|
||||||
|
this.duration = this.calcDuration();
|
||||||
|
} else if (this.strategy === "recurring-day-of-month") {
|
||||||
|
let list = this.getDayOfMonthList();
|
||||||
|
let array = this.start_time.split(":");
|
||||||
|
let hour = parseInt(array[0]);
|
||||||
|
let minute = parseInt(array[1]);
|
||||||
|
|
||||||
|
let dayList = [];
|
||||||
|
|
||||||
|
for (let day of list) {
|
||||||
|
if (typeof day === "string" && day.startsWith("lastDay")) {
|
||||||
|
if (day === "lastDay1") {
|
||||||
|
dayList.push("L");
|
||||||
|
}
|
||||||
|
// Unfortunately, lastDay2-4 is not supported by cron
|
||||||
|
} else {
|
||||||
|
dayList.push(day);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove duplicate
|
||||||
|
dayList = [ ...new Set(dayList) ];
|
||||||
|
|
||||||
|
this.cron = minute + " " + hour + " " + dayList.join(",") + " * *";
|
||||||
|
this.duration = this.calcDuration();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Maintenance;
|
@@ -1,13 +1,13 @@
|
|||||||
const https = require("https");
|
const https = require("https");
|
||||||
const dayjs = require("dayjs");
|
const dayjs = require("dayjs");
|
||||||
const utc = require("dayjs/plugin/utc");
|
|
||||||
let timezone = require("dayjs/plugin/timezone");
|
|
||||||
dayjs.extend(utc);
|
|
||||||
dayjs.extend(timezone);
|
|
||||||
const axios = require("axios");
|
const axios = require("axios");
|
||||||
const { Prometheus } = require("../prometheus");
|
const { Prometheus } = require("../prometheus");
|
||||||
const { log, UP, DOWN, PENDING, flipStatus, TimeLogger } = require("../../src/util");
|
const { log, UP, DOWN, PENDING, MAINTENANCE, flipStatus, TimeLogger, MAX_INTERVAL_SECOND, MIN_INTERVAL_SECOND,
|
||||||
const { tcping, ping, dnsResolve, checkCertificate, checkStatusCode, getTotalClientInRoom, setting, mssqlQuery, postgresQuery, mqttAsync, setSetting, httpNtlm, radius } = require("../util-server");
|
SQL_DATETIME_FORMAT
|
||||||
|
} = require("../../src/util");
|
||||||
|
const { tcping, ping, dnsResolve, checkCertificate, checkStatusCode, getTotalClientInRoom, setting, mssqlQuery, postgresQuery, mysqlQuery, mqttAsync, setSetting, httpNtlm, radius, grpcQuery,
|
||||||
|
redisPingAsync, mongodbPing,
|
||||||
|
} = require("../util-server");
|
||||||
const { R } = require("redbean-node");
|
const { R } = require("redbean-node");
|
||||||
const { BeanModel } = require("redbean-node/dist/bean-model");
|
const { BeanModel } = require("redbean-node/dist/bean-model");
|
||||||
const { Notification } = require("../notification");
|
const { Notification } = require("../notification");
|
||||||
@@ -18,12 +18,15 @@ const apicache = require("../modules/apicache");
|
|||||||
const { UptimeKumaServer } = require("../uptime-kuma-server");
|
const { UptimeKumaServer } = require("../uptime-kuma-server");
|
||||||
const { CacheableDnsHttpAgent } = require("../cacheable-dns-http-agent");
|
const { CacheableDnsHttpAgent } = require("../cacheable-dns-http-agent");
|
||||||
const { DockerHost } = require("../docker");
|
const { DockerHost } = require("../docker");
|
||||||
|
const { UptimeCacheList } = require("../uptime-cache-list");
|
||||||
|
const Gamedig = require("gamedig");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* status:
|
* status:
|
||||||
* 0 = DOWN
|
* 0 = DOWN
|
||||||
* 1 = UP
|
* 1 = UP
|
||||||
* 2 = PENDING
|
* 2 = PENDING
|
||||||
|
* 3 = MAINTENANCE
|
||||||
*/
|
*/
|
||||||
class Monitor extends BeanModel {
|
class Monitor extends BeanModel {
|
||||||
|
|
||||||
@@ -70,13 +73,18 @@ class Monitor extends BeanModel {
|
|||||||
let data = {
|
let data = {
|
||||||
id: this.id,
|
id: this.id,
|
||||||
name: this.name,
|
name: this.name,
|
||||||
|
description: this.description,
|
||||||
|
pathName: await this.getPathName(),
|
||||||
|
parent: this.parent,
|
||||||
|
childrenIDs: await Monitor.getAllChildrenIDs(this.id),
|
||||||
url: this.url,
|
url: this.url,
|
||||||
method: this.method,
|
method: this.method,
|
||||||
hostname: this.hostname,
|
hostname: this.hostname,
|
||||||
port: this.port,
|
port: this.port,
|
||||||
maxretries: this.maxretries,
|
maxretries: this.maxretries,
|
||||||
weight: this.weight,
|
weight: this.weight,
|
||||||
active: this.active,
|
active: await this.isActive(),
|
||||||
|
forceInactive: !await Monitor.isParentActive(this.id),
|
||||||
type: this.type,
|
type: this.type,
|
||||||
interval: this.interval,
|
interval: this.interval,
|
||||||
retryInterval: this.retryInterval,
|
retryInterval: this.retryInterval,
|
||||||
@@ -85,31 +93,31 @@ class Monitor extends BeanModel {
|
|||||||
expiryNotification: this.isEnabledExpiryNotification(),
|
expiryNotification: this.isEnabledExpiryNotification(),
|
||||||
ignoreTls: this.getIgnoreTls(),
|
ignoreTls: this.getIgnoreTls(),
|
||||||
upsideDown: this.isUpsideDown(),
|
upsideDown: this.isUpsideDown(),
|
||||||
|
packetSize: this.packetSize,
|
||||||
maxredirects: this.maxredirects,
|
maxredirects: this.maxredirects,
|
||||||
accepted_statuscodes: this.getAcceptedStatuscodes(),
|
accepted_statuscodes: this.getAcceptedStatuscodes(),
|
||||||
dns_resolve_type: this.dns_resolve_type,
|
dns_resolve_type: this.dns_resolve_type,
|
||||||
dns_resolve_server: this.dns_resolve_server,
|
dns_resolve_server: this.dns_resolve_server,
|
||||||
dns_last_result: this.dns_last_result,
|
dns_last_result: this.dns_last_result,
|
||||||
pushToken: this.pushToken,
|
|
||||||
docker_container: this.docker_container,
|
docker_container: this.docker_container,
|
||||||
docker_host: this.docker_host,
|
docker_host: this.docker_host,
|
||||||
proxyId: this.proxy_id,
|
proxyId: this.proxy_id,
|
||||||
notificationIDList,
|
notificationIDList,
|
||||||
tags: tags,
|
tags: tags,
|
||||||
mqttUsername: this.mqttUsername,
|
maintenance: await Monitor.isUnderMaintenance(this.id),
|
||||||
mqttPassword: this.mqttPassword,
|
|
||||||
mqttTopic: this.mqttTopic,
|
mqttTopic: this.mqttTopic,
|
||||||
mqttSuccessMessage: this.mqttSuccessMessage,
|
mqttSuccessMessage: this.mqttSuccessMessage,
|
||||||
databaseConnectionString: this.databaseConnectionString,
|
|
||||||
databaseQuery: this.databaseQuery,
|
databaseQuery: this.databaseQuery,
|
||||||
authMethod: this.authMethod,
|
authMethod: this.authMethod,
|
||||||
authWorkstation: this.authWorkstation,
|
grpcUrl: this.grpcUrl,
|
||||||
authDomain: this.authDomain,
|
grpcProtobuf: this.grpcProtobuf,
|
||||||
radiusUsername: this.radiusUsername,
|
grpcMethod: this.grpcMethod,
|
||||||
radiusPassword: this.radiusPassword,
|
grpcServiceName: this.grpcServiceName,
|
||||||
|
grpcEnableTls: this.getGrpcEnableTls(),
|
||||||
radiusCalledStationId: this.radiusCalledStationId,
|
radiusCalledStationId: this.radiusCalledStationId,
|
||||||
radiusCallingStationId: this.radiusCallingStationId,
|
radiusCallingStationId: this.radiusCallingStationId,
|
||||||
radiusSecret: this.radiusSecret,
|
game: this.game,
|
||||||
|
httpBodyEncoding: this.httpBodyEncoding
|
||||||
};
|
};
|
||||||
|
|
||||||
if (includeSensitiveData) {
|
if (includeSensitiveData) {
|
||||||
@@ -117,21 +125,45 @@ class Monitor extends BeanModel {
|
|||||||
...data,
|
...data,
|
||||||
headers: this.headers,
|
headers: this.headers,
|
||||||
body: this.body,
|
body: this.body,
|
||||||
|
grpcBody: this.grpcBody,
|
||||||
|
grpcMetadata: this.grpcMetadata,
|
||||||
basic_auth_user: this.basic_auth_user,
|
basic_auth_user: this.basic_auth_user,
|
||||||
basic_auth_pass: this.basic_auth_pass,
|
basic_auth_pass: this.basic_auth_pass,
|
||||||
pushToken: this.pushToken,
|
pushToken: this.pushToken,
|
||||||
|
databaseConnectionString: this.databaseConnectionString,
|
||||||
|
radiusUsername: this.radiusUsername,
|
||||||
|
radiusPassword: this.radiusPassword,
|
||||||
|
radiusSecret: this.radiusSecret,
|
||||||
|
mqttUsername: this.mqttUsername,
|
||||||
|
mqttPassword: this.mqttPassword,
|
||||||
|
authWorkstation: this.authWorkstation,
|
||||||
|
authDomain: this.authDomain,
|
||||||
|
tlsCa: this.tlsCa,
|
||||||
|
tlsCert: this.tlsCert,
|
||||||
|
tlsKey: this.tlsKey,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data.includeSensitiveData = includeSensitiveData;
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the monitor is active based on itself and its parents
|
||||||
|
* @returns {Promise<Boolean>}
|
||||||
|
*/
|
||||||
|
async isActive() {
|
||||||
|
const parentActive = await Monitor.isParentActive(this.id);
|
||||||
|
|
||||||
|
return this.active && parentActive;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all tags applied to this monitor
|
* Get all tags applied to this monitor
|
||||||
* @returns {Promise<LooseObject<any>[]>}
|
* @returns {Promise<LooseObject<any>[]>}
|
||||||
*/
|
*/
|
||||||
async getTags() {
|
async getTags() {
|
||||||
return await R.getAll("SELECT mt.*, tag.name, tag.color FROM monitor_tag mt JOIN tag ON mt.tag_id = tag.id WHERE mt.monitor_id = ?", [ this.id ]);
|
return await R.getAll("SELECT mt.*, tag.name, tag.color FROM monitor_tag mt JOIN tag ON mt.tag_id = tag.id WHERE mt.monitor_id = ? ORDER BY tag.name", [ this.id ]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -167,6 +199,14 @@ class Monitor extends BeanModel {
|
|||||||
return Boolean(this.upsideDown);
|
return Boolean(this.upsideDown);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse to boolean
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
getGrpcEnableTls() {
|
||||||
|
return Boolean(this.grpcEnableTls);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get accepted status codes
|
* Get accepted status codes
|
||||||
* @returns {Object}
|
* @returns {Object}
|
||||||
@@ -183,7 +223,7 @@ class Monitor extends BeanModel {
|
|||||||
let previousBeat = null;
|
let previousBeat = null;
|
||||||
let retries = 0;
|
let retries = 0;
|
||||||
|
|
||||||
let prometheus = new Prometheus(this);
|
this.prometheus = new Prometheus(this);
|
||||||
|
|
||||||
const beat = async () => {
|
const beat = async () => {
|
||||||
|
|
||||||
@@ -230,7 +270,40 @@ class Monitor extends BeanModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (this.type === "http" || this.type === "keyword") {
|
if (await Monitor.isUnderMaintenance(this.id)) {
|
||||||
|
bean.msg = "Monitor under maintenance";
|
||||||
|
bean.status = MAINTENANCE;
|
||||||
|
} else if (this.type === "group") {
|
||||||
|
const children = await Monitor.getChildren(this.id);
|
||||||
|
|
||||||
|
if (children.length > 0) {
|
||||||
|
bean.status = UP;
|
||||||
|
bean.msg = "All children up and running";
|
||||||
|
for (const child of children) {
|
||||||
|
if (!child.active) {
|
||||||
|
// Ignore inactive childs
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const lastBeat = await Monitor.getPreviousHeartbeat(child.id);
|
||||||
|
|
||||||
|
// Only change state if the monitor is in worse conditions then the ones before
|
||||||
|
if (bean.status === UP && (lastBeat.status === PENDING || lastBeat.status === DOWN)) {
|
||||||
|
bean.status = lastBeat.status;
|
||||||
|
} else if (bean.status === PENDING && lastBeat.status === DOWN) {
|
||||||
|
bean.status = lastBeat.status;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bean.status !== UP) {
|
||||||
|
bean.msg = "Child inaccessible";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Set status pending if group is empty
|
||||||
|
bean.status = PENDING;
|
||||||
|
bean.msg = "Group empty";
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (this.type === "http" || this.type === "keyword") {
|
||||||
// Do not do any queries/high loading things before the "bean.ping"
|
// Do not do any queries/high loading things before the "bean.ping"
|
||||||
let startTime = dayjs().valueOf();
|
let startTime = dayjs().valueOf();
|
||||||
|
|
||||||
@@ -249,16 +322,34 @@ class Monitor extends BeanModel {
|
|||||||
|
|
||||||
log.debug("monitor", `[${this.name}] Prepare Options for axios`);
|
log.debug("monitor", `[${this.name}] Prepare Options for axios`);
|
||||||
|
|
||||||
|
let contentType = null;
|
||||||
|
let bodyValue = null;
|
||||||
|
|
||||||
|
if (this.body && (typeof this.body === "string" && this.body.trim().length > 0)) {
|
||||||
|
if (!this.httpBodyEncoding || this.httpBodyEncoding === "json") {
|
||||||
|
try {
|
||||||
|
bodyValue = JSON.parse(this.body);
|
||||||
|
contentType = "application/json";
|
||||||
|
} catch (e) {
|
||||||
|
throw new Error("Your JSON body is invalid. " + e.message);
|
||||||
|
}
|
||||||
|
} else if (this.httpBodyEncoding === "xml") {
|
||||||
|
bodyValue = this.body;
|
||||||
|
contentType = "text/xml; charset=utf-8";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Axios Options
|
||||||
const options = {
|
const options = {
|
||||||
url: this.url,
|
url: this.url,
|
||||||
method: (this.method || "get").toLowerCase(),
|
method: (this.method || "get").toLowerCase(),
|
||||||
...(this.body ? { data: JSON.parse(this.body) } : {}),
|
|
||||||
timeout: this.interval * 1000 * 0.8,
|
timeout: this.interval * 1000 * 0.8,
|
||||||
headers: {
|
headers: {
|
||||||
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
|
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
|
||||||
"User-Agent": "Uptime-Kuma/" + version,
|
"User-Agent": "Uptime-Kuma/" + version,
|
||||||
...(this.headers ? JSON.parse(this.headers) : {}),
|
...(contentType ? { "Content-Type": contentType } : {}),
|
||||||
...(basicAuthHeader),
|
...(basicAuthHeader),
|
||||||
|
...(this.headers ? JSON.parse(this.headers) : {})
|
||||||
},
|
},
|
||||||
maxRedirects: this.maxredirects,
|
maxRedirects: this.maxredirects,
|
||||||
validateStatus: (status) => {
|
validateStatus: (status) => {
|
||||||
@@ -266,6 +357,10 @@ class Monitor extends BeanModel {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (bodyValue) {
|
||||||
|
options.data = bodyValue;
|
||||||
|
}
|
||||||
|
|
||||||
if (this.proxy_id) {
|
if (this.proxy_id) {
|
||||||
const proxy = await R.load("proxy", this.proxy_id);
|
const proxy = await R.load("proxy", this.proxy_id);
|
||||||
|
|
||||||
@@ -284,23 +379,23 @@ class Monitor extends BeanModel {
|
|||||||
options.httpsAgent = new https.Agent(httpsAgentOptions);
|
options.httpsAgent = new https.Agent(httpsAgentOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.auth_method === "mtls") {
|
||||||
|
if (this.tlsCert !== null && this.tlsCert !== "") {
|
||||||
|
options.httpsAgent.options.cert = Buffer.from(this.tlsCert);
|
||||||
|
}
|
||||||
|
if (this.tlsCa !== null && this.tlsCa !== "") {
|
||||||
|
options.httpsAgent.options.ca = Buffer.from(this.tlsCa);
|
||||||
|
}
|
||||||
|
if (this.tlsKey !== null && this.tlsKey !== "") {
|
||||||
|
options.httpsAgent.options.key = Buffer.from(this.tlsKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
log.debug("monitor", `[${this.name}] Axios Options: ${JSON.stringify(options)}`);
|
log.debug("monitor", `[${this.name}] Axios Options: ${JSON.stringify(options)}`);
|
||||||
log.debug("monitor", `[${this.name}] Axios Request`);
|
log.debug("monitor", `[${this.name}] Axios Request`);
|
||||||
|
|
||||||
let res;
|
// Make Request
|
||||||
if (this.auth_method === "ntlm") {
|
let res = await this.makeAxiosRequest(options);
|
||||||
options.httpsAgent.keepAlive = true;
|
|
||||||
|
|
||||||
res = await httpNtlm(options, {
|
|
||||||
username: this.basic_auth_user,
|
|
||||||
password: this.basic_auth_pass,
|
|
||||||
domain: this.authDomain,
|
|
||||||
workstation: this.authWorkstation ? this.authWorkstation : undefined
|
|
||||||
});
|
|
||||||
|
|
||||||
} else {
|
|
||||||
res = await axios.request(options);
|
|
||||||
}
|
|
||||||
|
|
||||||
bean.msg = `${res.status} - ${res.statusText}`;
|
bean.msg = `${res.status} - ${res.statusText}`;
|
||||||
bean.ping = dayjs().valueOf() - startTime;
|
bean.ping = dayjs().valueOf() - startTime;
|
||||||
@@ -314,8 +409,8 @@ class Monitor extends BeanModel {
|
|||||||
tlsInfo = await this.updateTlsInfo(tlsInfoObject);
|
tlsInfo = await this.updateTlsInfo(tlsInfoObject);
|
||||||
|
|
||||||
if (!this.getIgnoreTls() && this.isEnabledExpiryNotification()) {
|
if (!this.getIgnoreTls() && this.isEnabledExpiryNotification()) {
|
||||||
log.debug("monitor", `[${this.name}] call sendCertNotification`);
|
log.debug("monitor", `[${this.name}] call checkCertExpiryNotifications`);
|
||||||
await this.sendCertNotification(tlsInfoObject);
|
await this.checkCertExpiryNotifications(tlsInfoObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -349,7 +444,7 @@ class Monitor extends BeanModel {
|
|||||||
bean.msg += ", keyword is found";
|
bean.msg += ", keyword is found";
|
||||||
bean.status = UP;
|
bean.status = UP;
|
||||||
} else {
|
} else {
|
||||||
data = data.replace(/<[^>]*>?|[\n\r]|\s+/gm, " ");
|
data = data.replace(/<[^>]*>?|[\n\r]|\s+/gm, " ").trim();
|
||||||
if (data.length > 50) {
|
if (data.length > 50) {
|
||||||
data = data.substring(0, 47) + "...";
|
data = data.substring(0, 47) + "...";
|
||||||
}
|
}
|
||||||
@@ -364,7 +459,7 @@ class Monitor extends BeanModel {
|
|||||||
bean.status = UP;
|
bean.status = UP;
|
||||||
|
|
||||||
} else if (this.type === "ping") {
|
} else if (this.type === "ping") {
|
||||||
bean.ping = await ping(this.hostname);
|
bean.ping = await ping(this.hostname, this.packetSize);
|
||||||
bean.msg = "";
|
bean.msg = "";
|
||||||
bean.status = UP;
|
bean.status = UP;
|
||||||
} else if (this.type === "dns") {
|
} else if (this.type === "dns") {
|
||||||
@@ -474,25 +569,44 @@ class Monitor extends BeanModel {
|
|||||||
bean.msg = res.data.response.servers[0].name;
|
bean.msg = res.data.response.servers[0].name;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
bean.ping = await ping(this.hostname);
|
bean.ping = await ping(this.hostname, this.packetSize);
|
||||||
} catch (_) { }
|
} catch (_) { }
|
||||||
} else {
|
} else {
|
||||||
throw new Error("Server not found on Steam");
|
throw new Error("Server not found on Steam");
|
||||||
}
|
}
|
||||||
|
} else if (this.type === "gamedig") {
|
||||||
|
try {
|
||||||
|
const state = await Gamedig.query({
|
||||||
|
type: this.game,
|
||||||
|
host: this.hostname,
|
||||||
|
port: this.port,
|
||||||
|
givenPortOnly: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
bean.msg = state.name;
|
||||||
|
bean.status = UP;
|
||||||
|
bean.ping = state.ping;
|
||||||
|
} catch (e) {
|
||||||
|
throw new Error(e.message);
|
||||||
|
}
|
||||||
} else if (this.type === "docker") {
|
} else if (this.type === "docker") {
|
||||||
log.debug(`[${this.name}] Prepare Options for Axios`);
|
log.debug("monitor", `[${this.name}] Prepare Options for Axios`);
|
||||||
|
|
||||||
const dockerHost = await R.load("docker_host", this.docker_host);
|
const dockerHost = await R.load("docker_host", this.docker_host);
|
||||||
|
|
||||||
const options = {
|
const options = {
|
||||||
url: `/containers/${this.docker_container}/json`,
|
url: `/containers/${this.docker_container}/json`,
|
||||||
|
timeout: this.interval * 1000 * 0.8,
|
||||||
headers: {
|
headers: {
|
||||||
"Accept": "*/*",
|
"Accept": "*/*",
|
||||||
"User-Agent": "Uptime-Kuma/" + version,
|
"User-Agent": "Uptime-Kuma/" + version,
|
||||||
},
|
},
|
||||||
httpsAgent: new https.Agent({
|
httpsAgent: CacheableDnsHttpAgent.getHttpsAgent({
|
||||||
maxCachedSessions: 0, // Use Custom agent to disable session reuse (https://github.com/nodejs/node/issues/3940)
|
maxCachedSessions: 0, // Use Custom agent to disable session reuse (https://github.com/nodejs/node/issues/3940)
|
||||||
rejectUnauthorized: ! this.getIgnoreTls(),
|
rejectUnauthorized: !this.getIgnoreTls(),
|
||||||
|
}),
|
||||||
|
httpAgent: CacheableDnsHttpAgent.getHttpAgent({
|
||||||
|
maxCachedSessions: 0,
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -502,11 +616,13 @@ class Monitor extends BeanModel {
|
|||||||
options.baseURL = DockerHost.patchDockerURL(dockerHost._dockerDaemon);
|
options.baseURL = DockerHost.patchDockerURL(dockerHost._dockerDaemon);
|
||||||
}
|
}
|
||||||
|
|
||||||
log.debug(`[${this.name}] Axios Request`);
|
log.debug("monitor", `[${this.name}] Axios Request`);
|
||||||
let res = await axios.request(options);
|
let res = await axios.request(options);
|
||||||
if (res.data.State.Running) {
|
if (res.data.State.Running) {
|
||||||
bean.status = UP;
|
bean.status = UP;
|
||||||
bean.msg = "";
|
bean.msg = res.data.State.Status;
|
||||||
|
} else {
|
||||||
|
throw Error("Container State is " + res.data.State.Status);
|
||||||
}
|
}
|
||||||
} else if (this.type === "mqtt") {
|
} else if (this.type === "mqtt") {
|
||||||
bean.msg = await mqttAsync(this.hostname, this.mqttTopic, this.mqttSuccessMessage, {
|
bean.msg = await mqttAsync(this.hostname, this.mqttTopic, this.mqttSuccessMessage, {
|
||||||
@@ -524,6 +640,37 @@ class Monitor extends BeanModel {
|
|||||||
bean.msg = "";
|
bean.msg = "";
|
||||||
bean.status = UP;
|
bean.status = UP;
|
||||||
bean.ping = dayjs().valueOf() - startTime;
|
bean.ping = dayjs().valueOf() - startTime;
|
||||||
|
} else if (this.type === "grpc-keyword") {
|
||||||
|
let startTime = dayjs().valueOf();
|
||||||
|
const options = {
|
||||||
|
grpcUrl: this.grpcUrl,
|
||||||
|
grpcProtobufData: this.grpcProtobuf,
|
||||||
|
grpcServiceName: this.grpcServiceName,
|
||||||
|
grpcEnableTls: this.grpcEnableTls,
|
||||||
|
grpcMethod: this.grpcMethod,
|
||||||
|
grpcBody: this.grpcBody,
|
||||||
|
keyword: this.keyword
|
||||||
|
};
|
||||||
|
const response = await grpcQuery(options);
|
||||||
|
bean.ping = dayjs().valueOf() - startTime;
|
||||||
|
log.debug("monitor:", `gRPC response: ${JSON.stringify(response)}`);
|
||||||
|
let responseData = response.data;
|
||||||
|
if (responseData.length > 50) {
|
||||||
|
responseData = responseData.toString().substring(0, 47) + "...";
|
||||||
|
}
|
||||||
|
if (response.code !== 1) {
|
||||||
|
bean.status = DOWN;
|
||||||
|
bean.msg = `Error in send gRPC ${response.code} ${response.errorMessage}`;
|
||||||
|
} else {
|
||||||
|
if (response.data.toString().includes(this.keyword)) {
|
||||||
|
bean.status = UP;
|
||||||
|
bean.msg = `${responseData}, keyword [${this.keyword}] is found`;
|
||||||
|
} else {
|
||||||
|
log.debug("monitor:", `GRPC response [${response.data}] + ", but keyword [${this.keyword}] is not in [" + ${response.data} + "]"`);
|
||||||
|
bean.status = DOWN;
|
||||||
|
bean.msg = `, but keyword [${this.keyword}] is not in [" + ${responseData} + "]`;
|
||||||
|
}
|
||||||
|
}
|
||||||
} else if (this.type === "postgres") {
|
} else if (this.type === "postgres") {
|
||||||
let startTime = dayjs().valueOf();
|
let startTime = dayjs().valueOf();
|
||||||
|
|
||||||
@@ -532,8 +679,34 @@ class Monitor extends BeanModel {
|
|||||||
bean.msg = "";
|
bean.msg = "";
|
||||||
bean.status = UP;
|
bean.status = UP;
|
||||||
bean.ping = dayjs().valueOf() - startTime;
|
bean.ping = dayjs().valueOf() - startTime;
|
||||||
|
} else if (this.type === "mysql") {
|
||||||
|
let startTime = dayjs().valueOf();
|
||||||
|
|
||||||
|
bean.msg = await mysqlQuery(this.databaseConnectionString, this.databaseQuery);
|
||||||
|
bean.status = UP;
|
||||||
|
bean.ping = dayjs().valueOf() - startTime;
|
||||||
|
} else if (this.type === "mongodb") {
|
||||||
|
let startTime = dayjs().valueOf();
|
||||||
|
|
||||||
|
await mongodbPing(this.databaseConnectionString);
|
||||||
|
|
||||||
|
bean.msg = "";
|
||||||
|
bean.status = UP;
|
||||||
|
bean.ping = dayjs().valueOf() - startTime;
|
||||||
|
|
||||||
} else if (this.type === "radius") {
|
} else if (this.type === "radius") {
|
||||||
let startTime = dayjs().valueOf();
|
let startTime = dayjs().valueOf();
|
||||||
|
|
||||||
|
// Handle monitors that were created before the
|
||||||
|
// update and as such don't have a value for
|
||||||
|
// this.port.
|
||||||
|
let port;
|
||||||
|
if (this.port == null) {
|
||||||
|
port = 1812;
|
||||||
|
} else {
|
||||||
|
port = this.port;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const resp = await radius(
|
const resp = await radius(
|
||||||
this.hostname,
|
this.hostname,
|
||||||
@@ -541,7 +714,8 @@ class Monitor extends BeanModel {
|
|||||||
this.radiusPassword,
|
this.radiusPassword,
|
||||||
this.radiusCalledStationId,
|
this.radiusCalledStationId,
|
||||||
this.radiusCallingStationId,
|
this.radiusCallingStationId,
|
||||||
this.radiusSecret
|
this.radiusSecret,
|
||||||
|
port
|
||||||
);
|
);
|
||||||
if (resp.code) {
|
if (resp.code) {
|
||||||
bean.msg = resp.code;
|
bean.msg = resp.code;
|
||||||
@@ -556,9 +730,23 @@ class Monitor extends BeanModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
bean.ping = dayjs().valueOf() - startTime;
|
bean.ping = dayjs().valueOf() - startTime;
|
||||||
|
} else if (this.type === "redis") {
|
||||||
|
let startTime = dayjs().valueOf();
|
||||||
|
|
||||||
|
bean.msg = await redisPingAsync(this.databaseConnectionString);
|
||||||
|
bean.status = UP;
|
||||||
|
bean.ping = dayjs().valueOf() - startTime;
|
||||||
|
|
||||||
|
} else if (this.type in UptimeKumaServer.monitorTypeList) {
|
||||||
|
let startTime = dayjs().valueOf();
|
||||||
|
const monitorType = UptimeKumaServer.monitorTypeList[this.type];
|
||||||
|
await monitorType.check(this, bean);
|
||||||
|
if (!bean.ping) {
|
||||||
|
bean.ping = dayjs().valueOf() - startTime;
|
||||||
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
bean.msg = "Unknown Monitor Type";
|
throw new Error("Unknown Monitor Type");
|
||||||
bean.status = PENDING;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.isUpsideDown()) {
|
if (this.isUpsideDown()) {
|
||||||
@@ -594,8 +782,12 @@ class Monitor extends BeanModel {
|
|||||||
if (isImportant) {
|
if (isImportant) {
|
||||||
bean.important = true;
|
bean.important = true;
|
||||||
|
|
||||||
log.debug("monitor", `[${this.name}] sendNotification`);
|
if (Monitor.isImportantForNotification(isFirstBeat, previousBeat?.status, bean.status)) {
|
||||||
await Monitor.sendNotification(isFirstBeat, this, bean);
|
log.debug("monitor", `[${this.name}] sendNotification`);
|
||||||
|
await Monitor.sendNotification(isFirstBeat, this, bean);
|
||||||
|
} else {
|
||||||
|
log.debug("monitor", `[${this.name}] will not sendNotification because it is (or was) under maintenance`);
|
||||||
|
}
|
||||||
|
|
||||||
// Reset down count
|
// Reset down count
|
||||||
bean.downCount = 0;
|
bean.downCount = 0;
|
||||||
@@ -604,6 +796,8 @@ class Monitor extends BeanModel {
|
|||||||
log.debug("monitor", `[${this.name}] apicache clear`);
|
log.debug("monitor", `[${this.name}] apicache clear`);
|
||||||
apicache.clear();
|
apicache.clear();
|
||||||
|
|
||||||
|
UptimeKumaServer.getInstance().sendMaintenanceListByUserID(this.user_id);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
bean.important = false;
|
bean.important = false;
|
||||||
|
|
||||||
@@ -627,11 +821,14 @@ class Monitor extends BeanModel {
|
|||||||
beatInterval = this.retryInterval;
|
beatInterval = this.retryInterval;
|
||||||
}
|
}
|
||||||
log.warn("monitor", `Monitor #${this.id} '${this.name}': Pending: ${bean.msg} | Max retries: ${this.maxretries} | Retry: ${retries} | Retry Interval: ${beatInterval} seconds | Type: ${this.type}`);
|
log.warn("monitor", `Monitor #${this.id} '${this.name}': Pending: ${bean.msg} | Max retries: ${this.maxretries} | Retry: ${retries} | Retry Interval: ${beatInterval} seconds | Type: ${this.type}`);
|
||||||
|
} else if (bean.status === MAINTENANCE) {
|
||||||
|
log.warn("monitor", `Monitor #${this.id} '${this.name}': Under Maintenance | Type: ${this.type}`);
|
||||||
} else {
|
} else {
|
||||||
log.warn("monitor", `Monitor #${this.id} '${this.name}': Failing: ${bean.msg} | Interval: ${beatInterval} seconds | Type: ${this.type} | Down Count: ${bean.downCount} | Resend Interval: ${this.resendInterval}`);
|
log.warn("monitor", `Monitor #${this.id} '${this.name}': Failing: ${bean.msg} | Interval: ${beatInterval} seconds | Type: ${this.type} | Down Count: ${bean.downCount} | Resend Interval: ${this.resendInterval}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
log.debug("monitor", `[${this.name}] Send to socket`);
|
log.debug("monitor", `[${this.name}] Send to socket`);
|
||||||
|
UptimeCacheList.clearCache(this.id);
|
||||||
io.to(this.user_id).emit("heartbeat", bean.toJSON());
|
io.to(this.user_id).emit("heartbeat", bean.toJSON());
|
||||||
Monitor.sendStats(io, this.id, this.user_id);
|
Monitor.sendStats(io, this.id, this.user_id);
|
||||||
|
|
||||||
@@ -639,7 +836,7 @@ class Monitor extends BeanModel {
|
|||||||
await R.store(bean);
|
await R.store(bean);
|
||||||
|
|
||||||
log.debug("monitor", `[${this.name}] prometheus.update`);
|
log.debug("monitor", `[${this.name}] prometheus.update`);
|
||||||
prometheus.update(bean, tlsInfo);
|
this.prometheus?.update(bean, tlsInfo);
|
||||||
|
|
||||||
previousBeat = bean;
|
previousBeat = bean;
|
||||||
|
|
||||||
@@ -678,20 +875,60 @@ class Monitor extends BeanModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make a request using axios
|
||||||
|
* @param {Object} options Options for Axios
|
||||||
|
* @param {boolean} finalCall Should this be the final call i.e
|
||||||
|
* don't retry on faliure
|
||||||
|
* @returns {Object} Axios response
|
||||||
|
*/
|
||||||
|
async makeAxiosRequest(options, finalCall = false) {
|
||||||
|
try {
|
||||||
|
let res;
|
||||||
|
if (this.auth_method === "ntlm") {
|
||||||
|
options.httpsAgent.keepAlive = true;
|
||||||
|
|
||||||
|
res = await httpNtlm(options, {
|
||||||
|
username: this.basic_auth_user,
|
||||||
|
password: this.basic_auth_pass,
|
||||||
|
domain: this.authDomain,
|
||||||
|
workstation: this.authWorkstation ? this.authWorkstation : undefined
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
res = await axios.request(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
} catch (e) {
|
||||||
|
// Fix #2253
|
||||||
|
// Read more: https://stackoverflow.com/questions/1759956/curl-error-18-transfer-closed-with-outstanding-read-data-remaining
|
||||||
|
if (!finalCall && typeof e.message === "string" && e.message.includes("maxContentLength size of -1 exceeded")) {
|
||||||
|
log.debug("monitor", "makeAxiosRequest with gzip");
|
||||||
|
options.headers["Accept-Encoding"] = "gzip, deflate";
|
||||||
|
return this.makeAxiosRequest(options, true);
|
||||||
|
} else {
|
||||||
|
if (typeof e.message === "string" && e.message.includes("maxContentLength size of -1 exceeded")) {
|
||||||
|
e.message = "response timeout: incomplete response within a interval";
|
||||||
|
}
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** Stop monitor */
|
/** Stop monitor */
|
||||||
stop() {
|
stop() {
|
||||||
clearTimeout(this.heartbeatInterval);
|
clearTimeout(this.heartbeatInterval);
|
||||||
this.isStop = true;
|
this.isStop = true;
|
||||||
|
|
||||||
this.prometheus().remove();
|
this.prometheus?.remove();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a new prometheus instance
|
* Get prometheus instance
|
||||||
* @returns {Prometheus}
|
* @returns {Prometheus|undefined}
|
||||||
*/
|
*/
|
||||||
prometheus() {
|
getPrometheus() {
|
||||||
return new Prometheus(this);
|
return this.prometheus;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -816,7 +1053,15 @@ class Monitor extends BeanModel {
|
|||||||
* @param {number} duration Hours
|
* @param {number} duration Hours
|
||||||
* @param {number} monitorID ID of monitor to calculate
|
* @param {number} monitorID ID of monitor to calculate
|
||||||
*/
|
*/
|
||||||
static async calcUptime(duration, monitorID) {
|
static async calcUptime(duration, monitorID, forceNoCache = false) {
|
||||||
|
|
||||||
|
if (!forceNoCache) {
|
||||||
|
let cachedUptime = UptimeCacheList.getUptime(monitorID, duration);
|
||||||
|
if (cachedUptime != null) {
|
||||||
|
return cachedUptime;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const timeLogger = new TimeLogger();
|
const timeLogger = new TimeLogger();
|
||||||
|
|
||||||
const startTime = R.isoDateTime(dayjs.utc().subtract(duration, "hour"));
|
const startTime = R.isoDateTime(dayjs.utc().subtract(duration, "hour"));
|
||||||
@@ -837,7 +1082,7 @@ class Monitor extends BeanModel {
|
|||||||
-- SUM all uptime duration, also trim off the beat out of time window
|
-- SUM all uptime duration, also trim off the beat out of time window
|
||||||
SUM(
|
SUM(
|
||||||
CASE
|
CASE
|
||||||
WHEN (status = 1)
|
WHEN (status = 1 OR status = 3)
|
||||||
THEN
|
THEN
|
||||||
CASE
|
CASE
|
||||||
WHEN (JULIANDAY(\`time\`) - JULIANDAY(?)) * 86400 < duration
|
WHEN (JULIANDAY(\`time\`) - JULIANDAY(?)) * 86400 < duration
|
||||||
@@ -875,6 +1120,9 @@ class Monitor extends BeanModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Cache
|
||||||
|
UptimeCacheList.addUptime(monitorID, duration, uptime);
|
||||||
|
|
||||||
return uptime;
|
return uptime;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -908,11 +1156,49 @@ class Monitor extends BeanModel {
|
|||||||
// DOWN -> PENDING = this case not exists
|
// DOWN -> PENDING = this case not exists
|
||||||
// DOWN -> DOWN = not important
|
// DOWN -> DOWN = not important
|
||||||
// * DOWN -> UP = important
|
// * DOWN -> UP = important
|
||||||
let isImportant = isFirstBeat ||
|
// MAINTENANCE -> MAINTENANCE = not important
|
||||||
|
// * MAINTENANCE -> UP = important
|
||||||
|
// * MAINTENANCE -> DOWN = important
|
||||||
|
// * DOWN -> MAINTENANCE = important
|
||||||
|
// * UP -> MAINTENANCE = important
|
||||||
|
return isFirstBeat ||
|
||||||
|
(previousBeatStatus === DOWN && currentBeatStatus === MAINTENANCE) ||
|
||||||
|
(previousBeatStatus === UP && currentBeatStatus === MAINTENANCE) ||
|
||||||
|
(previousBeatStatus === MAINTENANCE && currentBeatStatus === DOWN) ||
|
||||||
|
(previousBeatStatus === MAINTENANCE && currentBeatStatus === UP) ||
|
||||||
|
(previousBeatStatus === UP && currentBeatStatus === DOWN) ||
|
||||||
|
(previousBeatStatus === DOWN && currentBeatStatus === UP) ||
|
||||||
|
(previousBeatStatus === PENDING && currentBeatStatus === DOWN);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is this beat important for notifications?
|
||||||
|
* @param {boolean} isFirstBeat Is this the first beat of this monitor?
|
||||||
|
* @param {const} previousBeatStatus Status of the previous beat
|
||||||
|
* @param {const} currentBeatStatus Status of the current beat
|
||||||
|
* @returns {boolean} True if is an important beat else false
|
||||||
|
*/
|
||||||
|
static isImportantForNotification(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
|
||||||
|
// MAINTENANCE -> MAINTENANCE = not important
|
||||||
|
// MAINTENANCE -> UP = not important
|
||||||
|
// * MAINTENANCE -> DOWN = important
|
||||||
|
// DOWN -> MAINTENANCE = not important
|
||||||
|
// UP -> MAINTENANCE = not important
|
||||||
|
return isFirstBeat ||
|
||||||
|
(previousBeatStatus === MAINTENANCE && currentBeatStatus === DOWN) ||
|
||||||
(previousBeatStatus === UP && currentBeatStatus === DOWN) ||
|
(previousBeatStatus === UP && currentBeatStatus === DOWN) ||
|
||||||
(previousBeatStatus === DOWN && currentBeatStatus === UP) ||
|
(previousBeatStatus === DOWN && currentBeatStatus === UP) ||
|
||||||
(previousBeatStatus === PENDING && currentBeatStatus === DOWN);
|
(previousBeatStatus === PENDING && currentBeatStatus === DOWN);
|
||||||
return isImportant;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -936,7 +1222,19 @@ class Monitor extends BeanModel {
|
|||||||
|
|
||||||
for (let notification of notificationList) {
|
for (let notification of notificationList) {
|
||||||
try {
|
try {
|
||||||
await Notification.send(JSON.parse(notification.config), msg, await monitor.toJSON(false), bean.toJSON());
|
const heartbeatJSON = bean.toJSON();
|
||||||
|
|
||||||
|
// Prevent if the msg is undefined, notifications such as Discord cannot send out.
|
||||||
|
if (!heartbeatJSON["msg"]) {
|
||||||
|
heartbeatJSON["msg"] = "N/A";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Also provide the time in server timezone
|
||||||
|
heartbeatJSON["timezone"] = await UptimeKumaServer.getInstance().getTimezone();
|
||||||
|
heartbeatJSON["timezoneOffset"] = UptimeKumaServer.getInstance().getTimezoneOffset();
|
||||||
|
heartbeatJSON["localDateTime"] = dayjs.utc(heartbeatJSON["time"]).tz(heartbeatJSON["timezone"]).format(SQL_DATETIME_FORMAT);
|
||||||
|
|
||||||
|
await Notification.send(JSON.parse(notification.config), msg, await monitor.toJSON(false), heartbeatJSON);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log.error("monitor", "Cannot send notification to " + notification.name);
|
log.error("monitor", "Cannot send notification to " + notification.name);
|
||||||
log.error("monitor", e);
|
log.error("monitor", e);
|
||||||
@@ -958,13 +1256,19 @@ class Monitor extends BeanModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send notification about a certificate
|
* checks certificate chain for expiring certificates
|
||||||
* @param {Object} tlsInfoObject Information about certificate
|
* @param {Object} tlsInfoObject Information about certificate
|
||||||
*/
|
*/
|
||||||
async sendCertNotification(tlsInfoObject) {
|
async checkCertExpiryNotifications(tlsInfoObject) {
|
||||||
if (tlsInfoObject && tlsInfoObject.certInfo && tlsInfoObject.certInfo.daysRemaining) {
|
if (tlsInfoObject && tlsInfoObject.certInfo && tlsInfoObject.certInfo.daysRemaining) {
|
||||||
const notificationList = await Monitor.getNotificationList(this);
|
const notificationList = await Monitor.getNotificationList(this);
|
||||||
|
|
||||||
|
if (! notificationList.length > 0) {
|
||||||
|
// fail fast. If no notification is set, all the following checks can be skipped.
|
||||||
|
log.debug("monitor", "No notification, no need to send cert notification");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let notifyDays = await setting("tlsExpiryNotifyDays");
|
let notifyDays = await setting("tlsExpiryNotifyDays");
|
||||||
if (notifyDays == null || !Array.isArray(notifyDays)) {
|
if (notifyDays == null || !Array.isArray(notifyDays)) {
|
||||||
// Reset Default
|
// Reset Default
|
||||||
@@ -972,10 +1276,19 @@ class Monitor extends BeanModel {
|
|||||||
notifyDays = [ 7, 14, 21 ];
|
notifyDays = [ 7, 14, 21 ];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (notifyDays != null && Array.isArray(notifyDays)) {
|
if (Array.isArray(notifyDays)) {
|
||||||
for (const day of notifyDays) {
|
for (const targetDays of notifyDays) {
|
||||||
log.debug("monitor", "call sendCertNotificationByTargetDays", day);
|
let certInfo = tlsInfoObject.certInfo;
|
||||||
await this.sendCertNotificationByTargetDays(tlsInfoObject.certInfo.daysRemaining, day, notificationList);
|
while (certInfo) {
|
||||||
|
let subjectCN = certInfo.subject["CN"];
|
||||||
|
if (certInfo.daysRemaining > targetDays) {
|
||||||
|
log.debug("monitor", `No need to send cert notification for ${certInfo.certType} certificate "${subjectCN}" (${certInfo.daysRemaining} days valid) on ${targetDays} deadline.`);
|
||||||
|
} else {
|
||||||
|
log.debug("monitor", `call sendCertNotificationByTargetDays for ${targetDays} deadline on certificate ${subjectCN}.`);
|
||||||
|
await this.sendCertNotificationByTargetDays(subjectCN, certInfo.certType, certInfo.daysRemaining, targetDays, notificationList);
|
||||||
|
}
|
||||||
|
certInfo = certInfo.issuerCertificate;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -984,55 +1297,47 @@ class Monitor extends BeanModel {
|
|||||||
/**
|
/**
|
||||||
* Send a certificate notification when certificate expires in less
|
* Send a certificate notification when certificate expires in less
|
||||||
* than target days
|
* than target days
|
||||||
* @param {number} daysRemaining Number of days remaining on certifcate
|
* @param {string} certCN Common Name attribute from the certificate subject
|
||||||
|
* @param {string} certType certificate type
|
||||||
|
* @param {number} daysRemaining Number of days remaining on certificate
|
||||||
* @param {number} targetDays Number of days to alert after
|
* @param {number} targetDays Number of days to alert after
|
||||||
* @param {LooseObject<any>[]} notificationList List of notification providers
|
* @param {LooseObject<any>[]} notificationList List of notification providers
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
async sendCertNotificationByTargetDays(daysRemaining, targetDays, notificationList) {
|
async sendCertNotificationByTargetDays(certCN, certType, daysRemaining, targetDays, notificationList) {
|
||||||
|
|
||||||
if (daysRemaining > targetDays) {
|
let row = await R.getRow("SELECT * FROM notification_sent_history WHERE type = ? AND monitor_id = ? AND days <= ?", [
|
||||||
log.debug("monitor", `No need to send cert notification. ${daysRemaining} > ${targetDays}`);
|
"certificate",
|
||||||
|
this.id,
|
||||||
|
targetDays,
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Sent already, no need to send again
|
||||||
|
if (row) {
|
||||||
|
log.debug("monitor", "Sent already, no need to send again");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (notificationList.length > 0) {
|
let sent = false;
|
||||||
|
log.debug("monitor", "Send certificate notification");
|
||||||
|
|
||||||
let row = await R.getRow("SELECT * FROM notification_sent_history WHERE type = ? AND monitor_id = ? AND days = ?", [
|
for (let notification of notificationList) {
|
||||||
|
try {
|
||||||
|
log.debug("monitor", "Sending to " + notification.name);
|
||||||
|
await Notification.send(JSON.parse(notification.config), `[${this.name}][${this.url}] ${certType} certificate ${certCN} will be expired in ${daysRemaining} days`);
|
||||||
|
sent = true;
|
||||||
|
} catch (e) {
|
||||||
|
log.error("monitor", "Cannot send cert notification to " + notification.name);
|
||||||
|
log.error("monitor", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sent) {
|
||||||
|
await R.exec("INSERT INTO notification_sent_history (type, monitor_id, days) VALUES(?, ?, ?)", [
|
||||||
"certificate",
|
"certificate",
|
||||||
this.id,
|
this.id,
|
||||||
targetDays,
|
targetDays,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Sent already, no need to send again
|
|
||||||
if (row) {
|
|
||||||
log.debug("monitor", "Sent already, no need to send again");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let sent = false;
|
|
||||||
log.debug("monitor", "Send certificate notification");
|
|
||||||
|
|
||||||
for (let notification of notificationList) {
|
|
||||||
try {
|
|
||||||
log.debug("monitor", "Sending to " + notification.name);
|
|
||||||
await Notification.send(JSON.parse(notification.config), `[${this.name}][${this.url}] Certificate will be expired in ${daysRemaining} days`);
|
|
||||||
sent = true;
|
|
||||||
} catch (e) {
|
|
||||||
log.error("monitor", "Cannot send cert notification to " + notification.name);
|
|
||||||
log.error("monitor", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sent) {
|
|
||||||
await R.exec("INSERT INTO notification_sent_history (type, monitor_id, days) VALUES(?, ?, ?)", [
|
|
||||||
"certificate",
|
|
||||||
this.id,
|
|
||||||
targetDays,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
log.debug("monitor", "No notification, no need to send cert notification");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1043,12 +1348,136 @@ class Monitor extends BeanModel {
|
|||||||
*/
|
*/
|
||||||
static async getPreviousHeartbeat(monitorID) {
|
static async getPreviousHeartbeat(monitorID) {
|
||||||
return await R.getRow(`
|
return await R.getRow(`
|
||||||
SELECT status, time FROM heartbeat
|
SELECT ping, status, time FROM heartbeat
|
||||||
WHERE id = (select MAX(id) from heartbeat where monitor_id = ?)
|
WHERE id = (select MAX(id) from heartbeat where monitor_id = ?)
|
||||||
`, [
|
`, [
|
||||||
monitorID
|
monitorID
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if monitor is under maintenance
|
||||||
|
* @param {number} monitorID ID of monitor to check
|
||||||
|
* @returns {Promise<boolean>}
|
||||||
|
*/
|
||||||
|
static async isUnderMaintenance(monitorID) {
|
||||||
|
const maintenanceIDList = await R.getCol(`
|
||||||
|
SELECT maintenance_id FROM monitor_maintenance
|
||||||
|
WHERE monitor_id = ?
|
||||||
|
`, [ monitorID ]);
|
||||||
|
|
||||||
|
for (const maintenanceID of maintenanceIDList) {
|
||||||
|
const maintenance = await UptimeKumaServer.getInstance().getMaintenance(maintenanceID);
|
||||||
|
if (maintenance && await maintenance.isUnderMaintenance()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const parent = await Monitor.getParent(monitorID);
|
||||||
|
if (parent != null) {
|
||||||
|
return await Monitor.isUnderMaintenance(parent.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Make sure monitor interval is between bounds */
|
||||||
|
validate() {
|
||||||
|
if (this.interval > MAX_INTERVAL_SECOND) {
|
||||||
|
throw new Error(`Interval cannot be more than ${MAX_INTERVAL_SECOND} seconds`);
|
||||||
|
}
|
||||||
|
if (this.interval < MIN_INTERVAL_SECOND) {
|
||||||
|
throw new Error(`Interval cannot be less than ${MIN_INTERVAL_SECOND} seconds`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets Parent of the monitor
|
||||||
|
* @param {number} monitorID ID of monitor to get
|
||||||
|
* @returns {Promise<LooseObject<any>>}
|
||||||
|
*/
|
||||||
|
static async getParent(monitorID) {
|
||||||
|
return await R.getRow(`
|
||||||
|
SELECT parent.* FROM monitor parent
|
||||||
|
LEFT JOIN monitor child
|
||||||
|
ON child.parent = parent.id
|
||||||
|
WHERE child.id = ?
|
||||||
|
`, [
|
||||||
|
monitorID,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets all Children of the monitor
|
||||||
|
* @param {number} monitorID ID of monitor to get
|
||||||
|
* @returns {Promise<LooseObject<any>>}
|
||||||
|
*/
|
||||||
|
static async getChildren(monitorID) {
|
||||||
|
return await R.getAll(`
|
||||||
|
SELECT * FROM monitor
|
||||||
|
WHERE parent = ?
|
||||||
|
`, [
|
||||||
|
monitorID,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets Full Path-Name (Groups and Name)
|
||||||
|
* @returns {Promise<String>}
|
||||||
|
*/
|
||||||
|
async getPathName() {
|
||||||
|
let path = this.name;
|
||||||
|
|
||||||
|
if (this.parent === null) {
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
let parent = await Monitor.getParent(this.id);
|
||||||
|
while (parent !== null) {
|
||||||
|
path = `${parent.name} / ${path}`;
|
||||||
|
parent = await Monitor.getParent(parent.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets recursive all child ids
|
||||||
|
* @param {number} monitorID ID of the monitor to get
|
||||||
|
* @returns {Promise<Array>}
|
||||||
|
*/
|
||||||
|
static async getAllChildrenIDs(monitorID) {
|
||||||
|
const childs = await Monitor.getChildren(monitorID);
|
||||||
|
|
||||||
|
if (childs === null) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
let childrenIDs = [];
|
||||||
|
|
||||||
|
for (const child of childs) {
|
||||||
|
childrenIDs.push(child.id);
|
||||||
|
childrenIDs = childrenIDs.concat(await Monitor.getAllChildrenIDs(child.id));
|
||||||
|
}
|
||||||
|
|
||||||
|
return childrenIDs;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks recursive if parent (ancestors) are active
|
||||||
|
* @param {number} monitorID ID of the monitor to get
|
||||||
|
* @returns {Promise<Boolean>}
|
||||||
|
*/
|
||||||
|
static async isParentActive(monitorID) {
|
||||||
|
const parent = await Monitor.getParent(monitorID);
|
||||||
|
|
||||||
|
if (parent === null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const parentActive = await Monitor.isParentActive(parent.id);
|
||||||
|
return parent.active && parentActive;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Monitor;
|
module.exports = Monitor;
|
||||||
|
@@ -2,6 +2,8 @@ const { BeanModel } = require("redbean-node/dist/bean-model");
|
|||||||
const { R } = require("redbean-node");
|
const { R } = require("redbean-node");
|
||||||
const cheerio = require("cheerio");
|
const cheerio = require("cheerio");
|
||||||
const { UptimeKumaServer } = require("../uptime-kuma-server");
|
const { UptimeKumaServer } = require("../uptime-kuma-server");
|
||||||
|
const jsesc = require("jsesc");
|
||||||
|
const googleAnalytics = require("../google-analytics");
|
||||||
|
|
||||||
class StatusPage extends BeanModel {
|
class StatusPage extends BeanModel {
|
||||||
|
|
||||||
@@ -36,7 +38,7 @@ class StatusPage extends BeanModel {
|
|||||||
*/
|
*/
|
||||||
static async renderHTML(indexHTML, statusPage) {
|
static async renderHTML(indexHTML, statusPage) {
|
||||||
const $ = cheerio.load(indexHTML);
|
const $ = cheerio.load(indexHTML);
|
||||||
const description155 = statusPage.description?.substring(0, 155);
|
const description155 = statusPage.description?.substring(0, 155) ?? "";
|
||||||
|
|
||||||
$("title").text(statusPage.title);
|
$("title").text(statusPage.title);
|
||||||
$("meta[name=description]").attr("content", description155);
|
$("meta[name=description]").attr("content", description155);
|
||||||
@@ -51,18 +53,32 @@ class StatusPage extends BeanModel {
|
|||||||
|
|
||||||
const head = $("head");
|
const head = $("head");
|
||||||
|
|
||||||
|
if (statusPage.googleAnalyticsTagId) {
|
||||||
|
let escapedGoogleAnalyticsScript = googleAnalytics.getGoogleAnalyticsScript(statusPage.googleAnalyticsTagId);
|
||||||
|
head.append($(escapedGoogleAnalyticsScript));
|
||||||
|
}
|
||||||
|
|
||||||
// OG Meta Tags
|
// OG Meta Tags
|
||||||
head.append(`<meta property="og:title" content="${statusPage.title}" />`);
|
let ogTitle = $("<meta property=\"og:title\" content=\"\" />").attr("content", statusPage.title);
|
||||||
head.append(`<meta property="og:description" content="${description155}" />`);
|
head.append(ogTitle);
|
||||||
|
|
||||||
|
let ogDescription = $("<meta property=\"og:description\" content=\"\" />").attr("content", description155);
|
||||||
|
head.append(ogDescription);
|
||||||
|
|
||||||
// Preload data
|
// Preload data
|
||||||
const json = JSON.stringify(await StatusPage.getStatusPageData(statusPage));
|
// Add jsesc, fix https://github.com/louislam/uptime-kuma/issues/2186
|
||||||
head.append(`
|
const escapedJSONObject = jsesc(await StatusPage.getStatusPageData(statusPage), {
|
||||||
<script>
|
"isScriptContext": true
|
||||||
window.preloadData = ${json}
|
});
|
||||||
|
|
||||||
|
const script = $(`
|
||||||
|
<script id="preload-data" data-json="{}">
|
||||||
|
window.preloadData = ${escapedJSONObject};
|
||||||
</script>
|
</script>
|
||||||
`);
|
`);
|
||||||
|
|
||||||
|
head.append(script);
|
||||||
|
|
||||||
// manifest.json
|
// manifest.json
|
||||||
$("link[rel=manifest]").attr("href", `/api/status-page/${statusPage.slug}/manifest.json`);
|
$("link[rel=manifest]").attr("href", `/api/status-page/${statusPage.slug}/manifest.json`);
|
||||||
|
|
||||||
@@ -83,6 +99,8 @@ class StatusPage extends BeanModel {
|
|||||||
incident = incident.toPublicJSON();
|
incident = incident.toPublicJSON();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let maintenanceList = await StatusPage.getMaintenanceList(statusPage.id);
|
||||||
|
|
||||||
// Public Group List
|
// Public Group List
|
||||||
const publicGroupList = [];
|
const publicGroupList = [];
|
||||||
const showTags = !!statusPage.show_tags;
|
const showTags = !!statusPage.show_tags;
|
||||||
@@ -100,7 +118,8 @@ class StatusPage extends BeanModel {
|
|||||||
return {
|
return {
|
||||||
config: await statusPage.toPublicJSON(),
|
config: await statusPage.toPublicJSON(),
|
||||||
incident,
|
incident,
|
||||||
publicGroupList
|
publicGroupList,
|
||||||
|
maintenanceList,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -214,6 +233,7 @@ class StatusPage extends BeanModel {
|
|||||||
customCSS: this.custom_css,
|
customCSS: this.custom_css,
|
||||||
footerText: this.footer_text,
|
footerText: this.footer_text,
|
||||||
showPoweredBy: !!this.show_powered_by,
|
showPoweredBy: !!this.show_powered_by,
|
||||||
|
googleAnalyticsId: this.google_analytics_tag_id,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -234,6 +254,7 @@ class StatusPage extends BeanModel {
|
|||||||
customCSS: this.custom_css,
|
customCSS: this.custom_css,
|
||||||
footerText: this.footer_text,
|
footerText: this.footer_text,
|
||||||
showPoweredBy: !!this.show_powered_by,
|
showPoweredBy: !!this.show_powered_by,
|
||||||
|
googleAnalyticsId: this.google_analytics_tag_id,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -259,6 +280,34 @@ class StatusPage extends BeanModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get list of maintenances
|
||||||
|
* @param {number} statusPageId ID of status page to get maintenance for
|
||||||
|
* @returns {Object} Object representing maintenances sanitized for public
|
||||||
|
*/
|
||||||
|
static async getMaintenanceList(statusPageId) {
|
||||||
|
try {
|
||||||
|
const publicMaintenanceList = [];
|
||||||
|
|
||||||
|
let maintenanceIDList = await R.getCol(`
|
||||||
|
SELECT DISTINCT maintenance_id
|
||||||
|
FROM maintenance_status_page
|
||||||
|
WHERE status_page_id = ?
|
||||||
|
`, [ statusPageId ]);
|
||||||
|
|
||||||
|
for (const maintenanceID of maintenanceIDList) {
|
||||||
|
let maintenance = UptimeKumaServer.getInstance().getMaintenance(maintenanceID);
|
||||||
|
if (maintenance && await maintenance.isUnderMaintenance()) {
|
||||||
|
publicMaintenanceList.push(await maintenance.toPublicJSON());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return publicMaintenanceList;
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = StatusPage;
|
module.exports = StatusPage;
|
||||||
|
20
server/modules/dayjs/plugin/timezone.d.ts
vendored
Normal file
20
server/modules/dayjs/plugin/timezone.d.ts
vendored
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import { PluginFunc, ConfigType } from 'dayjs'
|
||||||
|
|
||||||
|
declare const plugin: PluginFunc
|
||||||
|
export = plugin
|
||||||
|
|
||||||
|
declare module 'dayjs' {
|
||||||
|
interface Dayjs {
|
||||||
|
tz(timezone?: string, keepLocalTime?: boolean): Dayjs
|
||||||
|
offsetName(type?: 'short' | 'long'): string | undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DayjsTimezone {
|
||||||
|
(date: ConfigType, timezone?: string): Dayjs
|
||||||
|
(date: ConfigType, format: string, timezone?: string): Dayjs
|
||||||
|
guess(): string
|
||||||
|
setDefault(timezone?: string): void
|
||||||
|
}
|
||||||
|
|
||||||
|
const tz: DayjsTimezone
|
||||||
|
}
|
115
server/modules/dayjs/plugin/timezone.js
Normal file
115
server/modules/dayjs/plugin/timezone.js
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
/**
|
||||||
|
* Copy from node_modules/dayjs/plugin/timezone.js
|
||||||
|
* Try to fix https://github.com/louislam/uptime-kuma/issues/2318
|
||||||
|
* Source: https://github.com/iamkun/dayjs/tree/dev/src/plugin/utc
|
||||||
|
* License: MIT
|
||||||
|
*/
|
||||||
|
!function (t, e) {
|
||||||
|
// eslint-disable-next-line no-undef
|
||||||
|
typeof exports == "object" && typeof module != "undefined" ? module.exports = e() : typeof define == "function" && define.amd ? define(e) : (t = typeof globalThis != "undefined" ? globalThis : t || self).dayjs_plugin_timezone = e();
|
||||||
|
}(this, (function () {
|
||||||
|
"use strict";
|
||||||
|
let t = {
|
||||||
|
year: 0,
|
||||||
|
month: 1,
|
||||||
|
day: 2,
|
||||||
|
hour: 3,
|
||||||
|
minute: 4,
|
||||||
|
second: 5
|
||||||
|
};
|
||||||
|
let e = {};
|
||||||
|
return function (n, i, o) {
|
||||||
|
let r;
|
||||||
|
let a = function (t, n, i) {
|
||||||
|
void 0 === i && (i = {});
|
||||||
|
let o = new Date(t);
|
||||||
|
let r = function (t, n) {
|
||||||
|
void 0 === n && (n = {});
|
||||||
|
let i = n.timeZoneName || "short";
|
||||||
|
let o = t + "|" + i;
|
||||||
|
let r = e[o];
|
||||||
|
return r || (r = new Intl.DateTimeFormat("en-US", {
|
||||||
|
hour12: !1,
|
||||||
|
timeZone: t,
|
||||||
|
year: "numeric",
|
||||||
|
month: "2-digit",
|
||||||
|
day: "2-digit",
|
||||||
|
hour: "2-digit",
|
||||||
|
minute: "2-digit",
|
||||||
|
second: "2-digit",
|
||||||
|
timeZoneName: i
|
||||||
|
}), e[o] = r), r;
|
||||||
|
}(n, i);
|
||||||
|
return r.formatToParts(o);
|
||||||
|
};
|
||||||
|
let u = function (e, n) {
|
||||||
|
let i = a(e, n);
|
||||||
|
let r = [];
|
||||||
|
let u = 0;
|
||||||
|
for (; u < i.length; u += 1) {
|
||||||
|
let f = i[u];
|
||||||
|
let s = f.type;
|
||||||
|
let m = f.value;
|
||||||
|
let c = t[s];
|
||||||
|
c >= 0 && (r[c] = parseInt(m, 10));
|
||||||
|
}
|
||||||
|
let d = r[3];
|
||||||
|
let l = d === 24 ? 0 : d;
|
||||||
|
let v = r[0] + "-" + r[1] + "-" + r[2] + " " + l + ":" + r[4] + ":" + r[5] + ":000";
|
||||||
|
let h = +e;
|
||||||
|
return (o.utc(v).valueOf() - (h -= h % 1e3)) / 6e4;
|
||||||
|
};
|
||||||
|
let f = i.prototype;
|
||||||
|
f.tz = function (t, e) {
|
||||||
|
void 0 === t && (t = r);
|
||||||
|
let n = this.utcOffset();
|
||||||
|
let i = this.toDate();
|
||||||
|
let a = i.toLocaleString("en-US", { timeZone: t }).replace("\u202f", " ");
|
||||||
|
let u = Math.round((i - new Date(a)) / 1e3 / 60);
|
||||||
|
let f = o(a).$set("millisecond", this.$ms).utcOffset(15 * -Math.round(i.getTimezoneOffset() / 15) - u, !0);
|
||||||
|
if (e) {
|
||||||
|
let s = f.utcOffset();
|
||||||
|
f = f.add(n - s, "minute");
|
||||||
|
}
|
||||||
|
return f.$x.$timezone = t, f;
|
||||||
|
}, f.offsetName = function (t) {
|
||||||
|
let e = this.$x.$timezone || o.tz.guess();
|
||||||
|
let n = a(this.valueOf(), e, { timeZoneName: t }).find((function (t) {
|
||||||
|
return t.type.toLowerCase() === "timezonename";
|
||||||
|
}));
|
||||||
|
return n && n.value;
|
||||||
|
};
|
||||||
|
let s = f.startOf;
|
||||||
|
f.startOf = function (t, e) {
|
||||||
|
if (!this.$x || !this.$x.$timezone) {
|
||||||
|
return s.call(this, t, e);
|
||||||
|
}
|
||||||
|
let n = o(this.format("YYYY-MM-DD HH:mm:ss:SSS"));
|
||||||
|
return s.call(n, t, e).tz(this.$x.$timezone, !0);
|
||||||
|
}, o.tz = function (t, e, n) {
|
||||||
|
let i = n && e;
|
||||||
|
let a = n || e || r;
|
||||||
|
let f = u(+o(), a);
|
||||||
|
if (typeof t != "string") {
|
||||||
|
return o(t).tz(a);
|
||||||
|
}
|
||||||
|
let s = function (t, e, n) {
|
||||||
|
let i = t - 60 * e * 1e3;
|
||||||
|
let o = u(i, n);
|
||||||
|
if (e === o) {
|
||||||
|
return [ i, e ];
|
||||||
|
}
|
||||||
|
let r = u(i -= 60 * (o - e) * 1e3, n);
|
||||||
|
return o === r ? [ i, o ] : [ t - 60 * Math.min(o, r) * 1e3, Math.max(o, r) ];
|
||||||
|
}(o.utc(t, i).valueOf(), f, a);
|
||||||
|
let m = s[0];
|
||||||
|
let c = s[1];
|
||||||
|
let d = o(m).utcOffset(c);
|
||||||
|
return d.$x.$timezone = a, d;
|
||||||
|
}, o.tz.guess = function () {
|
||||||
|
return Intl.DateTimeFormat().resolvedOptions().timeZone;
|
||||||
|
}, o.tz.setDefault = function (t) {
|
||||||
|
r = t;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}));
|
19
server/monitor-types/monitor-type.js
Normal file
19
server/monitor-types/monitor-type.js
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
class MonitorType {
|
||||||
|
|
||||||
|
name = undefined;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {Monitor} monitor
|
||||||
|
* @param {Heartbeat} heartbeat
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
async check(monitor, heartbeat) {
|
||||||
|
throw new Error("You need to override check()");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
MonitorType,
|
||||||
|
};
|
@@ -8,7 +8,6 @@ class ClickSendSMS extends NotificationProvider {
|
|||||||
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
|
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
|
||||||
let okMsg = "Sent Successfully.";
|
let okMsg = "Sent Successfully.";
|
||||||
try {
|
try {
|
||||||
console.log({ notification });
|
|
||||||
let config = {
|
let config = {
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
|
@@ -15,7 +15,7 @@ class DingDing extends NotificationProvider {
|
|||||||
msgtype: "markdown",
|
msgtype: "markdown",
|
||||||
markdown: {
|
markdown: {
|
||||||
title: `[${this.statusToString(heartbeatJSON["status"])}] ${monitorJSON["name"]}`,
|
title: `[${this.statusToString(heartbeatJSON["status"])}] ${monitorJSON["name"]}`,
|
||||||
text: `## [${this.statusToString(heartbeatJSON["status"])}] ${monitorJSON["name"]} \n > ${heartbeatJSON["msg"]} \n > Time(UTC):${heartbeatJSON["time"]}`,
|
text: `## [${this.statusToString(heartbeatJSON["status"])}] ${monitorJSON["name"]} \n> ${heartbeatJSON["msg"]}\n> Time (${heartbeatJSON["timezone"]}): ${heartbeatJSON["localDateTime"]}`,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
if (this.sendToDingDing(notification, params)) {
|
if (this.sendToDingDing(notification, params)) {
|
||||||
|
@@ -59,12 +59,12 @@ class Discord extends NotificationProvider {
|
|||||||
value: monitorJSON["type"] === "push" ? "Heartbeat" : address,
|
value: monitorJSON["type"] === "push" ? "Heartbeat" : address,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Time (UTC)",
|
name: `Time (${heartbeatJSON["timezone"]})`,
|
||||||
value: heartbeatJSON["time"],
|
value: heartbeatJSON["localDateTime"],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Error",
|
name: "Error",
|
||||||
value: heartbeatJSON["msg"],
|
value: heartbeatJSON["msg"] == null ? "N/A" : heartbeatJSON["msg"],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}],
|
}],
|
||||||
@@ -91,11 +91,11 @@ class Discord extends NotificationProvider {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: monitorJSON["type"] === "push" ? "Service Type" : "Service URL",
|
name: monitorJSON["type"] === "push" ? "Service Type" : "Service URL",
|
||||||
value: monitorJSON["type"] === "push" ? "Heartbeat" : address.startsWith("http") ? "[Visit Service](" + address + ")" : address,
|
value: monitorJSON["type"] === "push" ? "Heartbeat" : address,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Time (UTC)",
|
name: `Time (${heartbeatJSON["timezone"]})`,
|
||||||
value: heartbeatJSON["time"],
|
value: heartbeatJSON["localDateTime"],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Ping",
|
name: "Ping",
|
||||||
|
@@ -35,8 +35,7 @@ class Feishu extends NotificationProvider {
|
|||||||
text:
|
text:
|
||||||
"[Down] " +
|
"[Down] " +
|
||||||
heartbeatJSON["msg"] +
|
heartbeatJSON["msg"] +
|
||||||
"\nTime (UTC): " +
|
`\nTime (${heartbeatJSON["timezone"]}): ${heartbeatJSON["localDateTime"]}`
|
||||||
heartbeatJSON["time"],
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
@@ -62,8 +61,7 @@ class Feishu extends NotificationProvider {
|
|||||||
text:
|
text:
|
||||||
"[Up] " +
|
"[Up] " +
|
||||||
heartbeatJSON["msg"] +
|
heartbeatJSON["msg"] +
|
||||||
"\nTime (UTC): " +
|
`\nTime (${heartbeatJSON["timezone"]}): ${heartbeatJSON["localDateTime"]}`,
|
||||||
heartbeatJSON["time"],
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
31
server/notification-providers/kook.js
Normal file
31
server/notification-providers/kook.js
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
const NotificationProvider = require("./notification-provider");
|
||||||
|
const axios = require("axios");
|
||||||
|
|
||||||
|
class Kook extends NotificationProvider {
|
||||||
|
|
||||||
|
name = "Kook";
|
||||||
|
|
||||||
|
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
|
||||||
|
let okMsg = "Sent Successfully.";
|
||||||
|
let url = "https://www.kookapp.cn/api/v3/message/create";
|
||||||
|
let data = {
|
||||||
|
target_id: notification.kookGuildID,
|
||||||
|
content: msg,
|
||||||
|
};
|
||||||
|
let config = {
|
||||||
|
headers: {
|
||||||
|
"Authorization": "Bot " + notification.kookBotToken,
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
await axios.post(url, data, config);
|
||||||
|
return okMsg;
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
this.throwGeneralAxiosError(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Kook;
|
@@ -33,7 +33,10 @@ class Line extends NotificationProvider {
|
|||||||
"messages": [
|
"messages": [
|
||||||
{
|
{
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"text": "UptimeKuma Alert: [🔴 Down]\n" + "Name: " + monitorJSON["name"] + " \n" + heartbeatJSON["msg"] + "\nTime (UTC): " + heartbeatJSON["time"]
|
"text": "UptimeKuma Alert: [🔴 Down]\n" +
|
||||||
|
"Name: " + monitorJSON["name"] + " \n" +
|
||||||
|
heartbeatJSON["msg"] +
|
||||||
|
`\nTime (${heartbeatJSON["timezone"]}): ${heartbeatJSON["localDateTime"]}`
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
@@ -44,7 +47,10 @@ class Line extends NotificationProvider {
|
|||||||
"messages": [
|
"messages": [
|
||||||
{
|
{
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"text": "UptimeKuma Alert: [✅ Up]\n" + "Name: " + monitorJSON["name"] + " \n" + heartbeatJSON["msg"] + "\nTime (UTC): " + heartbeatJSON["time"]
|
"text": "UptimeKuma Alert: [✅ Up]\n" +
|
||||||
|
"Name: " + monitorJSON["name"] + " \n" +
|
||||||
|
heartbeatJSON["msg"] +
|
||||||
|
`\nTime (${heartbeatJSON["timezone"]}): ${heartbeatJSON["localDateTime"]}`
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
@@ -24,12 +24,18 @@ class LineNotify extends NotificationProvider {
|
|||||||
await axios.post(lineAPIUrl, qs.stringify(testMessage), config);
|
await axios.post(lineAPIUrl, qs.stringify(testMessage), config);
|
||||||
} else if (heartbeatJSON["status"] === DOWN) {
|
} else if (heartbeatJSON["status"] === DOWN) {
|
||||||
let downMessage = {
|
let downMessage = {
|
||||||
"message": "\n[🔴 Down]\n" + "Name: " + monitorJSON["name"] + " \n" + heartbeatJSON["msg"] + "\nTime (UTC): " + heartbeatJSON["time"]
|
"message": "\n[🔴 Down]\n" +
|
||||||
|
"Name: " + monitorJSON["name"] + " \n" +
|
||||||
|
heartbeatJSON["msg"] + "\n" +
|
||||||
|
`Time (${heartbeatJSON["timezone"]}): ${heartbeatJSON["localDateTime"]}`
|
||||||
};
|
};
|
||||||
await axios.post(lineAPIUrl, qs.stringify(downMessage), config);
|
await axios.post(lineAPIUrl, qs.stringify(downMessage), config);
|
||||||
} else if (heartbeatJSON["status"] === UP) {
|
} else if (heartbeatJSON["status"] === UP) {
|
||||||
let upMessage = {
|
let upMessage = {
|
||||||
"message": "\n[✅ Up]\n" + "Name: " + monitorJSON["name"] + " \n" + heartbeatJSON["msg"] + "\nTime (UTC): " + heartbeatJSON["time"]
|
"message": "\n[✅ Up]\n" +
|
||||||
|
"Name: " + monitorJSON["name"] + " \n" +
|
||||||
|
heartbeatJSON["msg"] + "\n" +
|
||||||
|
`Time (${heartbeatJSON["timezone"]}): ${heartbeatJSON["localDateTime"]}`
|
||||||
};
|
};
|
||||||
await axios.post(lineAPIUrl, qs.stringify(upMessage), config);
|
await axios.post(lineAPIUrl, qs.stringify(upMessage), config);
|
||||||
}
|
}
|
||||||
|
@@ -8,7 +8,12 @@ class LunaSea extends NotificationProvider {
|
|||||||
|
|
||||||
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
|
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;
|
let lunaseaurl = "";
|
||||||
|
if (notification.lunaseaTarget === "user") {
|
||||||
|
lunaseaurl = "https://notify.lunasea.app/v1/custom/user/" + notification.lunaseaUserID;
|
||||||
|
} else {
|
||||||
|
lunaseaurl = "https://notify.lunasea.app/v1/custom/device/" + notification.lunaseaDevice;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (heartbeatJSON == null) {
|
if (heartbeatJSON == null) {
|
||||||
@@ -16,25 +21,29 @@ class LunaSea extends NotificationProvider {
|
|||||||
"title": "Uptime Kuma Alert",
|
"title": "Uptime Kuma Alert",
|
||||||
"body": msg,
|
"body": msg,
|
||||||
};
|
};
|
||||||
await axios.post(lunaseadevice, testdata);
|
await axios.post(lunaseaurl, testdata);
|
||||||
return okMsg;
|
return okMsg;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (heartbeatJSON["status"] === DOWN) {
|
if (heartbeatJSON["status"] === DOWN) {
|
||||||
let downdata = {
|
let downdata = {
|
||||||
"title": "UptimeKuma Alert: " + monitorJSON["name"],
|
"title": "UptimeKuma Alert: " + monitorJSON["name"],
|
||||||
"body": "[🔴 Down] " + heartbeatJSON["msg"] + "\nTime (UTC): " + heartbeatJSON["time"],
|
"body": "[🔴 Down] " +
|
||||||
|
heartbeatJSON["msg"] +
|
||||||
|
`\nTime (${heartbeatJSON["timezone"]}): ${heartbeatJSON["localDateTime"]}`
|
||||||
};
|
};
|
||||||
await axios.post(lunaseadevice, downdata);
|
await axios.post(lunaseaurl, downdata);
|
||||||
return okMsg;
|
return okMsg;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (heartbeatJSON["status"] === UP) {
|
if (heartbeatJSON["status"] === UP) {
|
||||||
let updata = {
|
let updata = {
|
||||||
"title": "UptimeKuma Alert: " + monitorJSON["name"],
|
"title": "UptimeKuma Alert: " + monitorJSON["name"],
|
||||||
"body": "[✅ Up] " + heartbeatJSON["msg"] + "\nTime (UTC): " + heartbeatJSON["time"],
|
"body": "[✅ Up] " +
|
||||||
|
heartbeatJSON["msg"] +
|
||||||
|
`\nTime (${heartbeatJSON["timezone"]}): ${heartbeatJSON["localDateTime"]}`
|
||||||
};
|
};
|
||||||
await axios.post(lunaseadevice, updata);
|
await axios.post(lunaseaurl, updata);
|
||||||
return okMsg;
|
return okMsg;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -10,7 +10,7 @@ class Mattermost extends NotificationProvider {
|
|||||||
let okMsg = "Sent Successfully.";
|
let okMsg = "Sent Successfully.";
|
||||||
try {
|
try {
|
||||||
const mattermostUserName = notification.mattermostusername || "Uptime Kuma";
|
const mattermostUserName = notification.mattermostusername || "Uptime Kuma";
|
||||||
// If heartbeatJSON is null, assume we're testing.
|
// If heartbeatJSON is null, assume non monitoring notification (Certificate warning) or testing.
|
||||||
if (heartbeatJSON == null) {
|
if (heartbeatJSON == null) {
|
||||||
let mattermostTestData = {
|
let mattermostTestData = {
|
||||||
username: mattermostUserName,
|
username: mattermostUserName,
|
||||||
@@ -27,97 +27,79 @@ class Mattermost extends NotificationProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const mattermostIconEmoji = notification.mattermosticonemo;
|
const mattermostIconEmoji = notification.mattermosticonemo;
|
||||||
const mattermostIconUrl = notification.mattermosticonurl;
|
let mattermostIconEmojiOnline = "";
|
||||||
|
let mattermostIconEmojiOffline = "";
|
||||||
|
|
||||||
if (heartbeatJSON["status"] === DOWN) {
|
if (mattermostIconEmoji && typeof mattermostIconEmoji === "string") {
|
||||||
let mattermostdowndata = {
|
const emojiArray = mattermostIconEmoji.split(" ");
|
||||||
username: mattermostUserName,
|
if (emojiArray.length >= 2) {
|
||||||
text: "Uptime Kuma Alert",
|
mattermostIconEmojiOnline = emojiArray[0];
|
||||||
channel: mattermostChannel,
|
mattermostIconEmojiOffline = emojiArray[1];
|
||||||
icon_emoji: mattermostIconEmoji,
|
}
|
||||||
icon_url: mattermostIconUrl,
|
|
||||||
attachments: [
|
|
||||||
{
|
|
||||||
fallback:
|
|
||||||
"Your " +
|
|
||||||
monitorJSON["name"] +
|
|
||||||
" service went down.",
|
|
||||||
color: "#FF0000",
|
|
||||||
title:
|
|
||||||
"❌ " +
|
|
||||||
monitorJSON["name"] +
|
|
||||||
" service went down. ❌",
|
|
||||||
title_link: monitorJSON["url"],
|
|
||||||
fields: [
|
|
||||||
{
|
|
||||||
short: true,
|
|
||||||
title: "Service Name",
|
|
||||||
value: monitorJSON["name"],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
short: true,
|
|
||||||
title: "Time (UTC)",
|
|
||||||
value: heartbeatJSON["time"],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
short: false,
|
|
||||||
title: "Error",
|
|
||||||
value: heartbeatJSON["msg"],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
await axios.post(
|
|
||||||
notification.mattermostWebhookUrl,
|
|
||||||
mattermostdowndata
|
|
||||||
);
|
|
||||||
return okMsg;
|
|
||||||
} else if (heartbeatJSON["status"] === UP) {
|
|
||||||
let mattermostupdata = {
|
|
||||||
username: mattermostUserName,
|
|
||||||
text: "Uptime Kuma Alert",
|
|
||||||
channel: mattermostChannel,
|
|
||||||
icon_emoji: mattermostIconEmoji,
|
|
||||||
icon_url: mattermostIconUrl,
|
|
||||||
attachments: [
|
|
||||||
{
|
|
||||||
fallback:
|
|
||||||
"Your " +
|
|
||||||
monitorJSON["name"] +
|
|
||||||
" service went up!",
|
|
||||||
color: "#32CD32",
|
|
||||||
title:
|
|
||||||
"✅ " +
|
|
||||||
monitorJSON["name"] +
|
|
||||||
" service went up! ✅",
|
|
||||||
title_link: monitorJSON["url"],
|
|
||||||
fields: [
|
|
||||||
{
|
|
||||||
short: true,
|
|
||||||
title: "Service Name",
|
|
||||||
value: monitorJSON["name"],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
short: true,
|
|
||||||
title: "Time (UTC)",
|
|
||||||
value: heartbeatJSON["time"],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
short: false,
|
|
||||||
title: "Ping",
|
|
||||||
value: heartbeatJSON["ping"] + "ms",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
await axios.post(
|
|
||||||
notification.mattermostWebhookUrl,
|
|
||||||
mattermostupdata
|
|
||||||
);
|
|
||||||
return okMsg;
|
|
||||||
}
|
}
|
||||||
|
const mattermostIconUrl = notification.mattermosticonurl;
|
||||||
|
let iconEmoji = mattermostIconEmoji;
|
||||||
|
let statusField = {
|
||||||
|
short: false,
|
||||||
|
title: "Error",
|
||||||
|
value: heartbeatJSON.msg,
|
||||||
|
};
|
||||||
|
let statusText = "unknown";
|
||||||
|
let color = "#000000";
|
||||||
|
if (heartbeatJSON.status === DOWN) {
|
||||||
|
iconEmoji = mattermostIconEmojiOffline || mattermostIconEmoji;
|
||||||
|
statusField = {
|
||||||
|
short: false,
|
||||||
|
title: "Error",
|
||||||
|
value: heartbeatJSON.msg,
|
||||||
|
};
|
||||||
|
statusText = "down.";
|
||||||
|
color = "#FF0000";
|
||||||
|
} else if (heartbeatJSON.status === UP) {
|
||||||
|
iconEmoji = mattermostIconEmojiOnline || mattermostIconEmoji;
|
||||||
|
statusField = {
|
||||||
|
short: false,
|
||||||
|
title: "Ping",
|
||||||
|
value: heartbeatJSON.ping + "ms",
|
||||||
|
};
|
||||||
|
statusText = "up!";
|
||||||
|
color = "#32CD32";
|
||||||
|
}
|
||||||
|
|
||||||
|
let mattermostdata = {
|
||||||
|
username: monitorJSON.name + " " + mattermostUserName,
|
||||||
|
channel: mattermostChannel,
|
||||||
|
icon_emoji: iconEmoji,
|
||||||
|
icon_url: mattermostIconUrl,
|
||||||
|
attachments: [
|
||||||
|
{
|
||||||
|
fallback:
|
||||||
|
"Your " +
|
||||||
|
monitorJSON.name +
|
||||||
|
" service went " +
|
||||||
|
statusText,
|
||||||
|
color: color,
|
||||||
|
title:
|
||||||
|
monitorJSON.name +
|
||||||
|
" service went " +
|
||||||
|
statusText,
|
||||||
|
title_link: monitorJSON.url,
|
||||||
|
fields: [
|
||||||
|
statusField,
|
||||||
|
{
|
||||||
|
short: true,
|
||||||
|
title: `Time (${heartbeatJSON["timezone"]})`,
|
||||||
|
value: heartbeatJSON.localDateTime,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
await axios.post(
|
||||||
|
notification.mattermostWebhookUrl,
|
||||||
|
mattermostdata
|
||||||
|
);
|
||||||
|
return okMsg;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.throwGeneralAxiosError(error);
|
this.throwGeneralAxiosError(error);
|
||||||
}
|
}
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user