feat: add http monitor which include cert exp date
This commit is contained in:
parent
594f3124ef
commit
31ba529022
@ -75,6 +75,7 @@
|
|||||||
"ts-node": "^10.9.1",
|
"ts-node": "^10.9.1",
|
||||||
"uuid": "^9.0.0",
|
"uuid": "^9.0.0",
|
||||||
"vite-express": "^0.10.0",
|
"vite-express": "^0.10.0",
|
||||||
|
"winston": "^3.11.0",
|
||||||
"yup": "^1.2.0",
|
"yup": "^1.2.0",
|
||||||
"zod": "^3.22.2",
|
"zod": "^3.22.2",
|
||||||
"zustand": "^4.4.1"
|
"zustand": "^4.4.1"
|
||||||
|
122
pnpm-lock.yaml
generated
122
pnpm-lock.yaml
generated
@ -166,6 +166,9 @@ dependencies:
|
|||||||
vite-express:
|
vite-express:
|
||||||
specifier: ^0.10.0
|
specifier: ^0.10.0
|
||||||
version: 0.10.0
|
version: 0.10.0
|
||||||
|
winston:
|
||||||
|
specifier: ^3.11.0
|
||||||
|
version: 3.11.0
|
||||||
yup:
|
yup:
|
||||||
specifier: ^1.2.0
|
specifier: ^1.2.0
|
||||||
version: 1.2.0
|
version: 1.2.0
|
||||||
@ -1361,6 +1364,11 @@ packages:
|
|||||||
to-fast-properties: 2.0.0
|
to-fast-properties: 2.0.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/@colors/colors@1.6.0:
|
||||||
|
resolution: {integrity: sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==}
|
||||||
|
engines: {node: '>=0.1.90'}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@cspotcode/source-map-support@0.8.1:
|
/@cspotcode/source-map-support@0.8.1:
|
||||||
resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==}
|
resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
@ -1372,6 +1380,14 @@ packages:
|
|||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/@dabh/diagnostics@2.0.3:
|
||||||
|
resolution: {integrity: sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==}
|
||||||
|
dependencies:
|
||||||
|
colorspace: 1.1.4
|
||||||
|
enabled: 2.0.0
|
||||||
|
kuler: 2.0.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@dagrejs/graphlib@2.1.4:
|
/@dagrejs/graphlib@2.1.4:
|
||||||
resolution: {integrity: sha512-QCg9sL4uhjn468FDEsb/S9hS2xUZSrv/+dApb1Ze5VKO96pTXKNJZ6MGhIpgWkc1TVhbVGH9/7rq/Mf8/jWicw==}
|
resolution: {integrity: sha512-QCg9sL4uhjn468FDEsb/S9hS2xUZSrv/+dApb1Ze5VKO96pTXKNJZ6MGhIpgWkc1TVhbVGH9/7rq/Mf8/jWicw==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -2204,6 +2220,10 @@ packages:
|
|||||||
minipass: 4.2.8
|
minipass: 4.2.8
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/@types/triple-beam@1.3.4:
|
||||||
|
resolution: {integrity: sha512-HlJjF3wxV4R2VQkFpKe0YqJLilYNgtRtsqqZtby7RkVsSs+i+vbyzjtUwpFEdUCKcrGzCiEJE7F/0mKjh0sunA==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@types/uuid@9.0.3:
|
/@types/uuid@9.0.3:
|
||||||
resolution: {integrity: sha512-taHQQH/3ZyI3zP8M/puluDEIEvtQHVYcC6y3N8ijFtAd28+Ey/G4sg1u2gB01S8MwybLOKAp9/yCMu/uR5l3Ug==}
|
resolution: {integrity: sha512-taHQQH/3ZyI3zP8M/puluDEIEvtQHVYcC6y3N8ijFtAd28+Ey/G4sg1u2gB01S8MwybLOKAp9/yCMu/uR5l3Ug==}
|
||||||
dev: false
|
dev: false
|
||||||
@ -2805,6 +2825,13 @@ packages:
|
|||||||
resolution: {integrity: sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==}
|
resolution: {integrity: sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/colorspace@1.1.4:
|
||||||
|
resolution: {integrity: sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==}
|
||||||
|
dependencies:
|
||||||
|
color: 3.2.1
|
||||||
|
text-hex: 1.0.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
/combined-stream@1.0.8:
|
/combined-stream@1.0.8:
|
||||||
resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
|
resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
|
||||||
engines: {node: '>= 0.8'}
|
engines: {node: '>= 0.8'}
|
||||||
@ -3295,6 +3322,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
|
resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/enabled@2.0.0:
|
||||||
|
resolution: {integrity: sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/encodeurl@1.0.2:
|
/encodeurl@1.0.2:
|
||||||
resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==}
|
resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==}
|
||||||
engines: {node: '>= 0.8'}
|
engines: {node: '>= 0.8'}
|
||||||
@ -3676,6 +3707,10 @@ packages:
|
|||||||
uglify-js: 2.8.29
|
uglify-js: 2.8.29
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/fn.name@1.1.0:
|
||||||
|
resolution: {integrity: sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/follow-redirects@1.15.2:
|
/follow-redirects@1.15.2:
|
||||||
resolution: {integrity: sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==}
|
resolution: {integrity: sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==}
|
||||||
engines: {node: '>=4.0'}
|
engines: {node: '>=4.0'}
|
||||||
@ -4196,6 +4231,11 @@ packages:
|
|||||||
call-bind: 1.0.2
|
call-bind: 1.0.2
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/is-stream@2.0.1:
|
||||||
|
resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==}
|
||||||
|
engines: {node: '>=8'}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/is-string@1.0.7:
|
/is-string@1.0.7:
|
||||||
resolution: {integrity: sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==}
|
resolution: {integrity: sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
@ -4339,6 +4379,10 @@ packages:
|
|||||||
is-buffer: 1.1.6
|
is-buffer: 1.1.6
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/kuler@2.0.0:
|
||||||
|
resolution: {integrity: sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/l7-tiny-sdf@0.0.4:
|
/l7-tiny-sdf@0.0.4:
|
||||||
resolution: {integrity: sha512-hMuA5jolQCyhK+QufHMy+qxZQlc9uD/S7jVWFDyVy5TKb3HxMOGc1RcqMwcvlgDXzmVqNWkxAN8LracSEwqYIw==}
|
resolution: {integrity: sha512-hMuA5jolQCyhK+QufHMy+qxZQlc9uD/S7jVWFDyVy5TKb3HxMOGc1RcqMwcvlgDXzmVqNWkxAN8LracSEwqYIw==}
|
||||||
dev: false
|
dev: false
|
||||||
@ -4421,6 +4465,18 @@ packages:
|
|||||||
resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
|
resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/logform@2.6.0:
|
||||||
|
resolution: {integrity: sha512-1ulHeNPp6k/LD8H91o7VYFBng5i1BDE7HoKxVbZiGFidS1Rj65qcywLxX+pVfAPoQJEjRdvKcusKwOupHCVOVQ==}
|
||||||
|
engines: {node: '>= 12.0.0'}
|
||||||
|
dependencies:
|
||||||
|
'@colors/colors': 1.6.0
|
||||||
|
'@types/triple-beam': 1.3.4
|
||||||
|
fecha: 4.2.3
|
||||||
|
ms: 2.1.3
|
||||||
|
safe-stable-stringify: 2.4.3
|
||||||
|
triple-beam: 1.4.1
|
||||||
|
dev: false
|
||||||
|
|
||||||
/longest@1.0.1:
|
/longest@1.0.1:
|
||||||
resolution: {integrity: sha512-k+yt5n3l48JU4k8ftnKG6V7u32wyH2NfKzeMto9F/QRE0amxy/LayxwlvjjkZEIzqR+19IrtFO8p5kB9QaYUFg==}
|
resolution: {integrity: sha512-k+yt5n3l48JU4k8ftnKG6V7u32wyH2NfKzeMto9F/QRE0amxy/LayxwlvjjkZEIzqR+19IrtFO8p5kB9QaYUFg==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
@ -4857,6 +4913,12 @@ packages:
|
|||||||
dependencies:
|
dependencies:
|
||||||
wrappy: 1.0.2
|
wrappy: 1.0.2
|
||||||
|
|
||||||
|
/one-time@1.0.0:
|
||||||
|
resolution: {integrity: sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==}
|
||||||
|
dependencies:
|
||||||
|
fn.name: 1.1.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
/openapi-types@12.1.3:
|
/openapi-types@12.1.3:
|
||||||
resolution: {integrity: sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==}
|
resolution: {integrity: sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==}
|
||||||
dev: false
|
dev: false
|
||||||
@ -6020,6 +6082,15 @@ packages:
|
|||||||
pify: 2.3.0
|
pify: 2.3.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/readable-stream@3.6.2:
|
||||||
|
resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==}
|
||||||
|
engines: {node: '>= 6'}
|
||||||
|
dependencies:
|
||||||
|
inherits: 2.0.4
|
||||||
|
string_decoder: 1.3.0
|
||||||
|
util-deprecate: 1.0.2
|
||||||
|
dev: false
|
||||||
|
|
||||||
/readdirp@3.6.0:
|
/readdirp@3.6.0:
|
||||||
resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
|
resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
|
||||||
engines: {node: '>=8.10.0'}
|
engines: {node: '>=8.10.0'}
|
||||||
@ -6169,6 +6240,11 @@ packages:
|
|||||||
is-regex: 1.1.4
|
is-regex: 1.1.4
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/safe-stable-stringify@2.4.3:
|
||||||
|
resolution: {integrity: sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g==}
|
||||||
|
engines: {node: '>=10'}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/safer-buffer@2.1.2:
|
/safer-buffer@2.1.2:
|
||||||
resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
|
resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
|
||||||
dev: false
|
dev: false
|
||||||
@ -6418,6 +6494,10 @@ packages:
|
|||||||
stackframe: 1.3.4
|
stackframe: 1.3.4
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/stack-trace@0.0.10:
|
||||||
|
resolution: {integrity: sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/stackframe@1.3.4:
|
/stackframe@1.3.4:
|
||||||
resolution: {integrity: sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==}
|
resolution: {integrity: sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==}
|
||||||
dev: false
|
dev: false
|
||||||
@ -6491,6 +6571,12 @@ packages:
|
|||||||
es-abstract: 1.22.1
|
es-abstract: 1.22.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/string_decoder@1.3.0:
|
||||||
|
resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==}
|
||||||
|
dependencies:
|
||||||
|
safe-buffer: 5.2.1
|
||||||
|
dev: false
|
||||||
|
|
||||||
/strip-ansi@3.0.1:
|
/strip-ansi@3.0.1:
|
||||||
resolution: {integrity: sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==}
|
resolution: {integrity: sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
@ -6638,6 +6724,10 @@ packages:
|
|||||||
yallist: 4.0.0
|
yallist: 4.0.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/text-hex@1.0.0:
|
||||||
|
resolution: {integrity: sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/thenify-all@1.6.0:
|
/thenify-all@1.6.0:
|
||||||
resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==}
|
resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==}
|
||||||
engines: {node: '>=0.8'}
|
engines: {node: '>=0.8'}
|
||||||
@ -6725,6 +6815,11 @@ packages:
|
|||||||
resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==}
|
resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/triple-beam@1.4.1:
|
||||||
|
resolution: {integrity: sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==}
|
||||||
|
engines: {node: '>= 14.0.0'}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/trpc-openapi@1.2.0(@trpc/server@10.38.4)(zod@3.22.2):
|
/trpc-openapi@1.2.0(@trpc/server@10.38.4)(zod@3.22.2):
|
||||||
resolution: {integrity: sha512-pfYoCd/3KYXWXvUPZBKJw455OOwngKN/6SIcj7Yit19OMLJ+8yVZkEvGEeg5wUSwfsiTdRsKuvqkRPXVSwV7ew==}
|
resolution: {integrity: sha512-pfYoCd/3KYXWXvUPZBKJw455OOwngKN/6SIcj7Yit19OMLJ+8yVZkEvGEeg5wUSwfsiTdRsKuvqkRPXVSwV7ew==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@ -6947,7 +7042,6 @@ packages:
|
|||||||
|
|
||||||
/util-deprecate@1.0.2:
|
/util-deprecate@1.0.2:
|
||||||
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
|
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
|
||||||
dev: true
|
|
||||||
|
|
||||||
/utility-types@3.10.0:
|
/utility-types@3.10.0:
|
||||||
resolution: {integrity: sha512-O11mqxmi7wMKCo6HKFt5AhO4BwY3VV68YU07tgxfz8zJTIxr4BpsezN49Ffwy9j3ZpwwJp4fkRwjRzq3uWE6Rg==}
|
resolution: {integrity: sha512-O11mqxmi7wMKCo6HKFt5AhO4BwY3VV68YU07tgxfz8zJTIxr4BpsezN49Ffwy9j3ZpwwJp4fkRwjRzq3uWE6Rg==}
|
||||||
@ -7083,6 +7177,32 @@ packages:
|
|||||||
engines: {node: '>= 0.8.0'}
|
engines: {node: '>= 0.8.0'}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/winston-transport@4.6.0:
|
||||||
|
resolution: {integrity: sha512-wbBA9PbPAHxKiygo7ub7BYRiKxms0tpfU2ljtWzb3SjRjv5yl6Ozuy/TkXf00HTAt+Uylo3gSkNwzc4ME0wiIg==}
|
||||||
|
engines: {node: '>= 12.0.0'}
|
||||||
|
dependencies:
|
||||||
|
logform: 2.6.0
|
||||||
|
readable-stream: 3.6.2
|
||||||
|
triple-beam: 1.4.1
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/winston@3.11.0:
|
||||||
|
resolution: {integrity: sha512-L3yR6/MzZAOl0DsysUXHVjOwv8mKZ71TrA/41EIduGpOOV5LQVodqN+QdQ6BS6PJ/RdIshZhq84P/fStEZkk7g==}
|
||||||
|
engines: {node: '>= 12.0.0'}
|
||||||
|
dependencies:
|
||||||
|
'@colors/colors': 1.6.0
|
||||||
|
'@dabh/diagnostics': 2.0.3
|
||||||
|
async: 3.2.4
|
||||||
|
is-stream: 2.0.1
|
||||||
|
logform: 2.6.0
|
||||||
|
one-time: 1.0.0
|
||||||
|
readable-stream: 3.6.2
|
||||||
|
safe-stable-stringify: 2.4.3
|
||||||
|
stack-trace: 0.0.10
|
||||||
|
triple-beam: 1.4.1
|
||||||
|
winston-transport: 4.6.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
/wordwrap@0.0.2:
|
/wordwrap@0.0.2:
|
||||||
resolution: {integrity: sha512-xSBsCeh+g+dinoBv3GAOWM4LcVVO68wLXRanibtBSdUvkGWQRGeE9P7IwU9EmDDi4jA6L44lz15CGMwdw9N5+Q==}
|
resolution: {integrity: sha512-xSBsCeh+g+dinoBv3GAOWM4LcVVO68wLXRanibtBSdUvkGWQRGeE9P7IwU9EmDDi4jA6L44lz15CGMwdw9N5+Q==}
|
||||||
engines: {node: '>=0.4.0'}
|
engines: {node: '>=0.4.0'}
|
||||||
|
@ -249,6 +249,7 @@ model Monitor {
|
|||||||
notifications Notification[]
|
notifications Notification[]
|
||||||
events MonitorEvent[]
|
events MonitorEvent[]
|
||||||
datas MonitorData[]
|
datas MonitorData[]
|
||||||
|
status MonitorStatus[]
|
||||||
|
|
||||||
@@index([workspaceId])
|
@@index([workspaceId])
|
||||||
}
|
}
|
||||||
@ -271,3 +272,16 @@ model MonitorData {
|
|||||||
|
|
||||||
monitor Monitor @relation(fields: [monitorId], references: [id], onUpdate: Cascade, onDelete: Cascade)
|
monitor Monitor @relation(fields: [monitorId], references: [id], onUpdate: Cascade, onDelete: Cascade)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Use for record latest monitor status, for example tls status
|
||||||
|
model MonitorStatus {
|
||||||
|
monitorId String @db.VarChar(30)
|
||||||
|
statusName String @db.VarChar(50)
|
||||||
|
payload Json @db.Json
|
||||||
|
createdAt DateTime @default(now()) @db.Timestamptz(6)
|
||||||
|
updatedAt DateTime @updatedAt @db.Timestamptz(6)
|
||||||
|
|
||||||
|
monitor Monitor @relation(fields: [monitorId], references: [id], onUpdate: Cascade, onDelete: Cascade)
|
||||||
|
|
||||||
|
@@id([monitorId, statusName])
|
||||||
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import React, { useMemo } from 'react';
|
import React, { useMemo } from 'react';
|
||||||
import type { Monitor } from '@prisma/client';
|
import type { Monitor } from '@prisma/client';
|
||||||
import { Button, Form, Input, InputNumber, Select } from 'antd';
|
import { Button, Form, Input, InputNumber, Select } from 'antd';
|
||||||
import { monitorProviders } from './provider';
|
import { getMonitorProvider, monitorProviders } from './provider';
|
||||||
import { useEvent } from '../../../hooks/useEvent';
|
import { useEvent } from '../../../hooks/useEvent';
|
||||||
import { NotificationPicker } from '../../notification/NotificationPicker';
|
import { NotificationPicker } from '../../notification/NotificationPicker';
|
||||||
|
|
||||||
@ -31,7 +31,7 @@ export const MonitorInfoEditor: React.FC<MonitorInfoEditorProps> = React.memo(
|
|||||||
const typeValue = Form.useWatch('type', form);
|
const typeValue = Form.useWatch('type', form);
|
||||||
|
|
||||||
const formEl = useMemo(() => {
|
const formEl = useMemo(() => {
|
||||||
const provider = monitorProviders.find((s) => s.name === typeValue);
|
const provider = getMonitorProvider(typeValue);
|
||||||
|
|
||||||
if (!provider) {
|
if (!provider) {
|
||||||
return null;
|
return null;
|
||||||
|
96
src/client/components/modals/monitor/provider/http.tsx
Normal file
96
src/client/components/modals/monitor/provider/http.tsx
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
import { Form, Input, Select } from 'antd';
|
||||||
|
import React from 'react';
|
||||||
|
import { MonitorOverviewComponent, MonitorProvider } from './types';
|
||||||
|
import { trpc } from '../../../../api/trpc';
|
||||||
|
import { MonitorStatsBlock } from '../../../monitor/MonitorStatsBlock';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
import { isEmpty } from 'lodash-es';
|
||||||
|
import { useCurrentWorkspaceId } from '../../../../store/user';
|
||||||
|
|
||||||
|
const MonitorHttp: React.FC = React.memo(() => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Form.Item
|
||||||
|
label="Url"
|
||||||
|
name={['payload', 'url']}
|
||||||
|
rules={[{ required: true }]}
|
||||||
|
>
|
||||||
|
<Input placeholder="https://example.com" />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
label="Method"
|
||||||
|
name={['payload', 'method']}
|
||||||
|
initialValue={'get'}
|
||||||
|
>
|
||||||
|
<Select>
|
||||||
|
<Select.Option value="get">GET</Select.Option>
|
||||||
|
<Select.Option value="post">POST</Select.Option>
|
||||||
|
<Select.Option value="put">PUT</Select.Option>
|
||||||
|
<Select.Option value="patch">PATCH</Select.Option>
|
||||||
|
<Select.Option value="delete">DELETE</Select.Option>
|
||||||
|
<Select.Option value="head">HEAD</Select.Option>
|
||||||
|
<Select.Option value="options">OPTIONS</Select.Option>
|
||||||
|
</Select>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
label="Content-Type"
|
||||||
|
name={['payload', 'contentType']}
|
||||||
|
initialValue={'application/json'}
|
||||||
|
>
|
||||||
|
<Select>
|
||||||
|
<Select.Option value="application/json">
|
||||||
|
application/json
|
||||||
|
</Select.Option>
|
||||||
|
<Select.Option value="application/x-www-form-urlencoded">
|
||||||
|
application/x-www-form-urlencoded
|
||||||
|
</Select.Option>
|
||||||
|
<Select.Option value="text/xml; charset=utf-8">
|
||||||
|
text/xml
|
||||||
|
</Select.Option>
|
||||||
|
</Select>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label="Content-Type" name={['payload', 'bodyValue']}>
|
||||||
|
<Input.TextArea placeholder='For example:\n{ "key": "value" }' />
|
||||||
|
</Form.Item>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
MonitorHttp.displayName = 'MonitorHttp';
|
||||||
|
|
||||||
|
export const MonitorHttpOverview: MonitorOverviewComponent = React.memo(
|
||||||
|
(props) => {
|
||||||
|
const workspaceId = useCurrentWorkspaceId();
|
||||||
|
const { data } = trpc.monitor.getStatus.useQuery({
|
||||||
|
workspaceId,
|
||||||
|
monitorId: props.monitorId,
|
||||||
|
statusName: 'tls',
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!data || !data.payload || typeof data.payload !== 'object') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const payload = data.payload as Record<string, any>;
|
||||||
|
|
||||||
|
if (isEmpty(payload.certInfo)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MonitorStatsBlock
|
||||||
|
title="Cert Exp."
|
||||||
|
desc={dayjs(payload.certInfo?.validTo).format('YYYY-MM-DD')}
|
||||||
|
text={`${payload.certInfo?.daysRemaining} days`}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
MonitorHttpOverview.displayName = 'MonitorHttpOverview';
|
||||||
|
|
||||||
|
export const httpProvider: MonitorProvider = {
|
||||||
|
label: 'HTTP',
|
||||||
|
name: 'http',
|
||||||
|
link: (info) => String(info.payload.url),
|
||||||
|
form: MonitorHttp,
|
||||||
|
overview: [MonitorHttpOverview],
|
||||||
|
};
|
@ -1,25 +1,25 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { MonitorInfo } from '../../../../../types';
|
import { MonitorInfo } from '../../../../../types';
|
||||||
import { MonitorPing } from './ping';
|
import { pingProvider } from './ping';
|
||||||
|
import { httpProvider } from './http';
|
||||||
interface MonitorProvider {
|
import { MonitorProvider } from './types';
|
||||||
label: string;
|
|
||||||
name: string;
|
|
||||||
link: (info: MonitorInfo) => React.ReactNode;
|
|
||||||
form: React.ComponentType;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const monitorProviders: MonitorProvider[] = [
|
export const monitorProviders: MonitorProvider[] = [
|
||||||
{
|
pingProvider, // ping
|
||||||
label: 'Ping',
|
httpProvider, // http
|
||||||
name: 'ping',
|
|
||||||
link: (info) => String(info.payload.hostname),
|
|
||||||
form: MonitorPing,
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
|
|
||||||
|
export function getMonitorProvider(type: string) {
|
||||||
|
const provider = monitorProviders.find((m) => m.name === type);
|
||||||
|
if (!provider) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return provider;
|
||||||
|
}
|
||||||
|
|
||||||
export function getMonitorLink(info: MonitorInfo): React.ReactNode {
|
export function getMonitorLink(info: MonitorInfo): React.ReactNode {
|
||||||
const provider = monitorProviders.find((m) => m.name === info.type);
|
const provider = getMonitorProvider(info.type);
|
||||||
if (!provider) {
|
if (!provider) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { Form, Input } from 'antd';
|
import { Form, Input } from 'antd';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { MonitorProvider } from './types';
|
||||||
|
|
||||||
export const MonitorPing: React.FC = React.memo(() => {
|
export const MonitorPing: React.FC = React.memo(() => {
|
||||||
return (
|
return (
|
||||||
@ -15,3 +16,10 @@ export const MonitorPing: React.FC = React.memo(() => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
MonitorPing.displayName = 'MonitorPing';
|
MonitorPing.displayName = 'MonitorPing';
|
||||||
|
|
||||||
|
export const pingProvider: MonitorProvider = {
|
||||||
|
label: 'Ping',
|
||||||
|
name: 'ping',
|
||||||
|
link: (info) => String(info.payload.hostname),
|
||||||
|
form: MonitorPing,
|
||||||
|
};
|
||||||
|
13
src/client/components/modals/monitor/provider/types.ts
Normal file
13
src/client/components/modals/monitor/provider/types.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import { MonitorInfo } from '../../../../../types';
|
||||||
|
|
||||||
|
export interface MonitorProvider {
|
||||||
|
label: string;
|
||||||
|
name: string;
|
||||||
|
link: (info: MonitorInfo) => React.ReactNode;
|
||||||
|
form: React.ComponentType;
|
||||||
|
overview?: MonitorOverviewComponent[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export type MonitorOverviewComponent = React.ComponentType<{
|
||||||
|
monitorId: string;
|
||||||
|
}>;
|
@ -19,7 +19,7 @@ export const MonitorEventList: React.FC<MonitorEventListProps> = React.memo(
|
|||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
if (isLoading === false && data.length === 0) {
|
if (isLoading === false && data.length === 0) {
|
||||||
return <Empty />;
|
return <Empty description="No events" />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -4,7 +4,7 @@ import React, { useMemo, useState } from 'react';
|
|||||||
import { trpc } from '../../api/trpc';
|
import { trpc } from '../../api/trpc';
|
||||||
import { useCurrentWorkspaceId } from '../../store/user';
|
import { useCurrentWorkspaceId } from '../../store/user';
|
||||||
import { Loading } from '../Loading';
|
import { Loading } from '../Loading';
|
||||||
import { getMonitorLink } from '../modals/monitor/provider';
|
import { getMonitorLink, getMonitorProvider } from '../modals/monitor/provider';
|
||||||
import { NotFoundTip } from '../NotFoundTip';
|
import { NotFoundTip } from '../NotFoundTip';
|
||||||
import { MonitorInfo as MonitorInfoType } from '../../../types';
|
import { MonitorInfo as MonitorInfoType } from '../../../types';
|
||||||
import { Area, AreaConfig } from '@ant-design/charts';
|
import { Area, AreaConfig } from '@ant-design/charts';
|
||||||
@ -14,6 +14,7 @@ import { last, uniqBy } from 'lodash-es';
|
|||||||
import { ErrorTip } from '../ErrorTip';
|
import { ErrorTip } from '../ErrorTip';
|
||||||
import { ColorTag } from '../ColorTag';
|
import { ColorTag } from '../ColorTag';
|
||||||
import { useNavigate } from 'react-router';
|
import { useNavigate } from 'react-router';
|
||||||
|
import { MonitorStatsBlock } from './MonitorStatsBlock';
|
||||||
|
|
||||||
interface MonitorInfoProps {
|
interface MonitorInfoProps {
|
||||||
monitorId: string;
|
monitorId: string;
|
||||||
@ -88,6 +89,7 @@ export const MonitorInfo: React.FC<MonitorInfoProps> = React.memo((props) => {
|
|||||||
<Card>
|
<Card>
|
||||||
<MonitorDataMetrics
|
<MonitorDataMetrics
|
||||||
monitorId={monitorId}
|
monitorId={monitorId}
|
||||||
|
monitorType={monitorInfo.type}
|
||||||
currectResponse={currectResponse}
|
currectResponse={currectResponse}
|
||||||
/>
|
/>
|
||||||
</Card>
|
</Card>
|
||||||
@ -103,14 +105,29 @@ MonitorInfo.displayName = 'MonitorInfo';
|
|||||||
|
|
||||||
export const MonitorDataMetrics: React.FC<{
|
export const MonitorDataMetrics: React.FC<{
|
||||||
monitorId: string;
|
monitorId: string;
|
||||||
|
monitorType: string;
|
||||||
currectResponse: number;
|
currectResponse: number;
|
||||||
}> = React.memo((props) => {
|
}> = React.memo((props) => {
|
||||||
const workspaceId = useCurrentWorkspaceId();
|
const workspaceId = useCurrentWorkspaceId();
|
||||||
const { monitorId, currectResponse } = props;
|
const { monitorId, monitorType, currectResponse } = props;
|
||||||
const { data, isLoading } = trpc.monitor.dataMetrics.useQuery({
|
const { data, isLoading } = trpc.monitor.dataMetrics.useQuery({
|
||||||
workspaceId,
|
workspaceId,
|
||||||
monitorId,
|
monitorId,
|
||||||
});
|
});
|
||||||
|
const providerOverview = useMemo(() => {
|
||||||
|
const provider = getMonitorProvider(monitorType);
|
||||||
|
if (!provider || !provider.overview) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{provider.overview.map((Component) => (
|
||||||
|
<Component monitorId={monitorId} />
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}, [monitorType]);
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return <Loading />;
|
return <Loading />;
|
||||||
@ -122,44 +139,40 @@ export const MonitorDataMetrics: React.FC<{
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex justify-between text-center">
|
<div className="flex justify-between text-center">
|
||||||
<div>
|
<MonitorStatsBlock
|
||||||
<div className="font-bold mb-0.5">Response</div>
|
title="Response"
|
||||||
<div className="text-gray-500">(Current)</div>
|
desc="(Current)"
|
||||||
<div>{currectResponse} ms</div>
|
text={`${currectResponse} ms`}
|
||||||
</div>
|
/>
|
||||||
<div>
|
<MonitorStatsBlock
|
||||||
<div className="font-bold mb-0.5">Avg. Response</div>
|
title="Avg. Response"
|
||||||
<div className="text-gray-500">(24 hour)</div>
|
desc="(24 hour)"
|
||||||
<div>{parseFloat(data.recent1DayAvg.toFixed(0))} ms</div>
|
text={`${parseFloat(data.recent1DayAvg.toFixed(0))} ms`}
|
||||||
</div>
|
/>
|
||||||
<div>
|
<MonitorStatsBlock
|
||||||
<div className="font-bold mb-0.5">Uptime</div>
|
title="Uptime"
|
||||||
<div className="text-gray-500 mb-2 text-xs">(24 hour)</div>
|
desc="(24 hour)"
|
||||||
<div>
|
text={`${parseFloat(
|
||||||
{parseFloat(
|
(
|
||||||
(
|
(data.recent1DayOnlineCount /
|
||||||
(data.recent1DayOnlineCount /
|
(data.recent1DayOnlineCount + data.recent1DayOfflineCount)) *
|
||||||
(data.recent1DayOnlineCount + data.recent1DayOfflineCount)) *
|
100
|
||||||
100
|
).toFixed(2)
|
||||||
).toFixed(2)
|
)} %`}
|
||||||
)}
|
/>
|
||||||
%
|
<MonitorStatsBlock
|
||||||
</div>
|
title="Uptime"
|
||||||
</div>
|
desc="(30 days)"
|
||||||
<div>
|
text={`${parseFloat(
|
||||||
<div className="font-bold mb-0.5">Uptime</div>
|
(
|
||||||
<div className="text-gray-500">(30 days)</div>
|
(data.recent30DayOnlineCount /
|
||||||
<div>
|
(data.recent30DayOnlineCount + data.recent30DayOfflineCount)) *
|
||||||
{parseFloat(
|
100
|
||||||
(
|
).toFixed(2)
|
||||||
(data.recent30DayOnlineCount /
|
)} %`}
|
||||||
(data.recent30DayOnlineCount + data.recent30DayOfflineCount)) *
|
/>
|
||||||
100
|
|
||||||
).toFixed(2)
|
{providerOverview}
|
||||||
)}
|
|
||||||
%
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
19
src/client/components/monitor/MonitorStatsBlock.tsx
Normal file
19
src/client/components/monitor/MonitorStatsBlock.tsx
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
interface MonitorStatsBlockProps {
|
||||||
|
title: string;
|
||||||
|
desc: string;
|
||||||
|
text: string;
|
||||||
|
}
|
||||||
|
export const MonitorStatsBlock: React.FC<MonitorStatsBlockProps> = React.memo(
|
||||||
|
(props) => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className="font-bold mb-0.5">{props.title}</div>
|
||||||
|
<div className="text-gray-500">{props.desc}</div>
|
||||||
|
<div>{props.text}</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
MonitorStatsBlock.displayName = 'MonitorStatsBlock';
|
164
src/server/model/monitor/provider/http.ts
Normal file
164
src/server/model/monitor/provider/http.ts
Normal file
@ -0,0 +1,164 @@
|
|||||||
|
import { MonitorProvider } from './type';
|
||||||
|
import axios, { AxiosRequestConfig, AxiosResponse } from 'axios';
|
||||||
|
import { logger } from '../../../utils/logger';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
import { prisma } from '../../_client';
|
||||||
|
|
||||||
|
export const http: MonitorProvider<{
|
||||||
|
url: string;
|
||||||
|
method?: string;
|
||||||
|
timeout?: number; // second
|
||||||
|
contentType?: string;
|
||||||
|
bodyValue?: string;
|
||||||
|
maxRedirects?: number;
|
||||||
|
}> = {
|
||||||
|
run: async (monitor) => {
|
||||||
|
if (typeof monitor.payload !== 'object') {
|
||||||
|
throw new Error('monitor.payload should be object');
|
||||||
|
}
|
||||||
|
|
||||||
|
const {
|
||||||
|
url,
|
||||||
|
method = 'get',
|
||||||
|
timeout = 30,
|
||||||
|
contentType,
|
||||||
|
bodyValue,
|
||||||
|
maxRedirects,
|
||||||
|
} = monitor.payload;
|
||||||
|
|
||||||
|
const config: AxiosRequestConfig = {
|
||||||
|
url: url,
|
||||||
|
method: (method || 'get').toLowerCase(),
|
||||||
|
timeout: timeout * 1000,
|
||||||
|
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',
|
||||||
|
...(contentType ? { 'Content-Type': contentType } : {}),
|
||||||
|
},
|
||||||
|
maxRedirects: maxRedirects,
|
||||||
|
// validateStatus: (status) => {
|
||||||
|
// return checkStatusCode(status, this.getAcceptedStatuscodes());
|
||||||
|
// },
|
||||||
|
};
|
||||||
|
|
||||||
|
if (bodyValue) {
|
||||||
|
config.data = bodyValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const startTime = dayjs();
|
||||||
|
const res = await axios(config);
|
||||||
|
|
||||||
|
const diff = dayjs().diff(startTime, 'ms');
|
||||||
|
|
||||||
|
if (url.startsWith('https:')) {
|
||||||
|
try {
|
||||||
|
const { valid, certInfo } = checkCertificate(res);
|
||||||
|
|
||||||
|
await prisma.monitorStatus.upsert({
|
||||||
|
where: {
|
||||||
|
monitorId_statusName: {
|
||||||
|
monitorId: monitor.id,
|
||||||
|
statusName: 'tls',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
update: {
|
||||||
|
payload: {
|
||||||
|
valid,
|
||||||
|
certInfo,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
create: {
|
||||||
|
monitorId: monitor.id,
|
||||||
|
statusName: 'tls',
|
||||||
|
payload: {
|
||||||
|
valid,
|
||||||
|
certInfo,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} catch (err) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
return diff;
|
||||||
|
} catch (err) {
|
||||||
|
logger.error('run monitor http error', err);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
function checkCertificate(res: AxiosResponse<any, any>) {
|
||||||
|
if (!res.request.res.socket) {
|
||||||
|
throw new Error('No socket found');
|
||||||
|
}
|
||||||
|
|
||||||
|
const info = res.request.res.socket.getPeerCertificate(true);
|
||||||
|
const valid = res.request.res.socket.authorized || false;
|
||||||
|
|
||||||
|
logger.info('cert', 'Parsing Certificate Info', info);
|
||||||
|
|
||||||
|
const parsedInfo = parseCertificateInfo(info);
|
||||||
|
|
||||||
|
return {
|
||||||
|
valid: valid,
|
||||||
|
certInfo: parsedInfo,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseCertificateInfo(info: any) {
|
||||||
|
let link = info;
|
||||||
|
let i = 0;
|
||||||
|
|
||||||
|
const existingList: Record<string, boolean> = {};
|
||||||
|
|
||||||
|
while (link) {
|
||||||
|
logger.debug('cert', `[${i}] ${link.fingerprint}`);
|
||||||
|
|
||||||
|
if (!link.valid_from || !link.valid_to) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
link.validTo = new Date(link.valid_to);
|
||||||
|
link.validFor = link.subjectaltname
|
||||||
|
?.replace(/DNS:|IP Address:/g, '')
|
||||||
|
.split(', ');
|
||||||
|
link.daysRemaining = getDaysRemaining(new Date(), link.validTo);
|
||||||
|
|
||||||
|
existingList[link.fingerprint] = true;
|
||||||
|
|
||||||
|
// Move up the chain until loop is encountered
|
||||||
|
if (link.issuerCertificate == null) {
|
||||||
|
link.certType = i === 0 ? 'self-signed' : 'root CA';
|
||||||
|
break;
|
||||||
|
} else if (link.issuerCertificate.fingerprint in existingList) {
|
||||||
|
// a root CA certificate is typically "signed by itself" (=> "self signed certificate") and thus the "issuerCertificate" is a reference to itself.
|
||||||
|
logger.debug('cert', `[Last] ${link.issuerCertificate.fingerprint}`);
|
||||||
|
link.certType = i === 0 ? 'self-signed' : 'root CA';
|
||||||
|
link.issuerCertificate = null;
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
link.certType = i === 0 ? 'server' : 'intermediate CA';
|
||||||
|
link = link.issuerCertificate;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Should be no use, but just in case.
|
||||||
|
if (i > 500) {
|
||||||
|
throw new Error('Dead loop occurred in parseCertificateInfo');
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get days remaining from a time range
|
||||||
|
* @param {Date} validFrom Start date
|
||||||
|
* @param {Date} validTo End date
|
||||||
|
* @returns {number} Number of days remaining
|
||||||
|
*/
|
||||||
|
function getDaysRemaining(validFrom: Date, validTo: Date) {
|
||||||
|
const daysRemaining = dayjs(validTo).diff(validFrom, 'days');
|
||||||
|
|
||||||
|
return daysRemaining;
|
||||||
|
}
|
@ -1,6 +1,8 @@
|
|||||||
|
import { http } from './http';
|
||||||
import { ping } from './ping';
|
import { ping } from './ping';
|
||||||
import type { MonitorProvider } from './type';
|
import type { MonitorProvider } from './type';
|
||||||
|
|
||||||
export const monitorProviders: Record<string, MonitorProvider<any>> = {
|
export const monitorProviders: Record<string, MonitorProvider<any>> = {
|
||||||
ping,
|
ping,
|
||||||
|
http,
|
||||||
};
|
};
|
||||||
|
@ -239,4 +239,23 @@ export const monitorRouter = router({
|
|||||||
|
|
||||||
return list;
|
return list;
|
||||||
}),
|
}),
|
||||||
|
getStatus: workspaceProcedure
|
||||||
|
.input(
|
||||||
|
z.object({
|
||||||
|
monitorId: z.string().cuid2(),
|
||||||
|
statusName: z.string(),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.query(async ({ input }) => {
|
||||||
|
const { monitorId, statusName } = input;
|
||||||
|
|
||||||
|
return prisma.monitorStatus.findUnique({
|
||||||
|
where: {
|
||||||
|
monitorId_statusName: {
|
||||||
|
monitorId,
|
||||||
|
statusName,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
|
40
src/server/utils/logger.ts
Normal file
40
src/server/utils/logger.ts
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import winston, { format } from 'winston';
|
||||||
|
import util from 'util';
|
||||||
|
|
||||||
|
type Format = ReturnType<typeof format.cli>;
|
||||||
|
|
||||||
|
function utilFormatter(): Format {
|
||||||
|
return {
|
||||||
|
transform(info) {
|
||||||
|
const args = info[Symbol.for('splat')];
|
||||||
|
if (args) {
|
||||||
|
info.message = util.format(info.message, ...args);
|
||||||
|
}
|
||||||
|
return info;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const logger = winston.createLogger({
|
||||||
|
level: 'info',
|
||||||
|
format: format.json(),
|
||||||
|
transports: [
|
||||||
|
//
|
||||||
|
// - Write all logs with importance level of `error` or less to `error.log`
|
||||||
|
// - Write all logs with importance level of `info` or less to `combined.log`
|
||||||
|
//
|
||||||
|
new winston.transports.File({ filename: 'error.log', level: 'error' }),
|
||||||
|
new winston.transports.Console({
|
||||||
|
format: format.combine(
|
||||||
|
format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss.SSS' }),
|
||||||
|
utilFormatter(),
|
||||||
|
format.colorize(),
|
||||||
|
format.printf(
|
||||||
|
({ level, message, label, timestamp }) =>
|
||||||
|
`${timestamp} ${label || '-'} ${level}: ${message}`
|
||||||
|
)
|
||||||
|
),
|
||||||
|
level: process.env.NODE_ENV === 'production' ? 'info' : 'debug',
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
});
|
Loading…
x
Reference in New Issue
Block a user