Compare commits
24
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
69e07975e3 | ||
|
|
c5c9a1a88b | ||
|
|
31efa791bd | ||
|
|
f8c0b57d21 | ||
|
|
605c969d2f | ||
|
|
ad7d4e54c3 | ||
|
|
c7689cc64a | ||
|
|
27c44a0fbd | ||
|
|
190d9dd2b1 | ||
|
|
49672db563 | ||
|
|
80603c9a42 | ||
|
|
f3b156f2d0 | ||
|
|
ec82efebb4 | ||
|
|
8dd846d6cd | ||
|
|
82175f166f | ||
|
|
3c99809e8e | ||
|
|
26db2b3a41 | ||
|
|
232a71c502 | ||
|
|
8df263fd93 | ||
|
|
d2f3335b1a | ||
|
|
7bfd68e374 | ||
|
|
b2e2b11ef2 | ||
|
|
93b9abd508 | ||
|
|
888b399a4f |
No files matched your search
Generated
+138
-170
@@ -58,7 +58,7 @@
|
|||||||
"lint-staged": "^16.2.7",
|
"lint-staged": "^16.2.7",
|
||||||
"prettier": "^3.3.3",
|
"prettier": "^3.3.3",
|
||||||
"typescript": "5.9.3",
|
"typescript": "5.9.3",
|
||||||
"vite": "6.3.6"
|
"vite": "6.4.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/code-frame": {
|
"node_modules/@babel/code-frame": {
|
||||||
@@ -83,6 +83,7 @@
|
|||||||
"node_modules/@babel/core": {
|
"node_modules/@babel/core": {
|
||||||
"version": "7.29.0",
|
"version": "7.29.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/code-frame": "^7.29.0",
|
"@babel/code-frame": "^7.29.0",
|
||||||
"@babel/generator": "^7.29.0",
|
"@babel/generator": "^7.29.0",
|
||||||
@@ -353,6 +354,7 @@
|
|||||||
"node_modules/@emotion/react": {
|
"node_modules/@emotion/react": {
|
||||||
"version": "11.14.0",
|
"version": "11.14.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.18.3",
|
"@babel/runtime": "^7.18.3",
|
||||||
"@emotion/babel-plugin": "^11.13.5",
|
"@emotion/babel-plugin": "^11.13.5",
|
||||||
@@ -390,6 +392,7 @@
|
|||||||
"node_modules/@emotion/styled": {
|
"node_modules/@emotion/styled": {
|
||||||
"version": "11.14.1",
|
"version": "11.14.1",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.18.3",
|
"@babel/runtime": "^7.18.3",
|
||||||
"@emotion/babel-plugin": "^11.13.5",
|
"@emotion/babel-plugin": "^11.13.5",
|
||||||
@@ -434,7 +437,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"ppc64"
|
"ppc64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -451,7 +453,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -468,7 +469,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -485,7 +485,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -502,7 +501,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -519,7 +517,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -536,7 +533,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -553,7 +549,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -570,7 +565,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -587,7 +581,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -604,7 +597,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"ia32"
|
"ia32"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -621,7 +613,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"loong64"
|
"loong64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -638,7 +629,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"mips64el"
|
"mips64el"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -655,7 +645,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"ppc64"
|
"ppc64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -672,7 +661,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"riscv64"
|
"riscv64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -689,7 +677,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"s390x"
|
"s390x"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -704,7 +691,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -721,7 +707,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -738,7 +723,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -755,7 +739,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -772,7 +755,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -789,7 +771,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -806,7 +787,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -823,7 +803,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -840,7 +819,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"ia32"
|
"ia32"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -857,7 +835,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1185,6 +1162,7 @@
|
|||||||
"node_modules/@mui/material": {
|
"node_modules/@mui/material": {
|
||||||
"version": "7.3.7",
|
"version": "7.3.7",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.28.4",
|
"@babel/runtime": "^7.28.4",
|
||||||
"@mui/core-downloads-tracker": "^7.3.7",
|
"@mui/core-downloads-tracker": "^7.3.7",
|
||||||
@@ -1289,6 +1267,7 @@
|
|||||||
"node_modules/@mui/system": {
|
"node_modules/@mui/system": {
|
||||||
"version": "7.3.7",
|
"version": "7.3.7",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.28.4",
|
"@babel/runtime": "^7.28.4",
|
||||||
"@mui/private-theming": "^7.3.7",
|
"@mui/private-theming": "^7.3.7",
|
||||||
@@ -1660,13 +1639,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-android-arm-eabi": {
|
"node_modules/@rollup/rollup-android-arm-eabi": {
|
||||||
"version": "4.57.1",
|
"version": "4.59.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.57.1.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz",
|
||||||
"integrity": "sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg==",
|
"integrity": "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1674,13 +1652,12 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-android-arm64": {
|
"node_modules/@rollup/rollup-android-arm64": {
|
||||||
"version": "4.57.1",
|
"version": "4.59.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.57.1.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz",
|
||||||
"integrity": "sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w==",
|
"integrity": "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1688,13 +1665,12 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-darwin-arm64": {
|
"node_modules/@rollup/rollup-darwin-arm64": {
|
||||||
"version": "4.57.1",
|
"version": "4.59.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.57.1.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz",
|
||||||
"integrity": "sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg==",
|
"integrity": "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1702,13 +1678,12 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-darwin-x64": {
|
"node_modules/@rollup/rollup-darwin-x64": {
|
||||||
"version": "4.57.1",
|
"version": "4.59.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.57.1.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz",
|
||||||
"integrity": "sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w==",
|
"integrity": "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1716,13 +1691,12 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-freebsd-arm64": {
|
"node_modules/@rollup/rollup-freebsd-arm64": {
|
||||||
"version": "4.57.1",
|
"version": "4.59.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.57.1.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz",
|
||||||
"integrity": "sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug==",
|
"integrity": "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1730,13 +1704,12 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-freebsd-x64": {
|
"node_modules/@rollup/rollup-freebsd-x64": {
|
||||||
"version": "4.57.1",
|
"version": "4.59.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.57.1.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz",
|
||||||
"integrity": "sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q==",
|
"integrity": "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1744,13 +1717,12 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
|
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
|
||||||
"version": "4.57.1",
|
"version": "4.59.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.57.1.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz",
|
||||||
"integrity": "sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==",
|
"integrity": "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1758,13 +1730,12 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
|
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
|
||||||
"version": "4.57.1",
|
"version": "4.59.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.57.1.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz",
|
||||||
"integrity": "sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==",
|
"integrity": "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1772,13 +1743,12 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-arm64-gnu": {
|
"node_modules/@rollup/rollup-linux-arm64-gnu": {
|
||||||
"version": "4.57.1",
|
"version": "4.59.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.57.1.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz",
|
||||||
"integrity": "sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==",
|
"integrity": "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1786,13 +1756,12 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-arm64-musl": {
|
"node_modules/@rollup/rollup-linux-arm64-musl": {
|
||||||
"version": "4.57.1",
|
"version": "4.59.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.57.1.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz",
|
||||||
"integrity": "sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==",
|
"integrity": "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1800,13 +1769,12 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-loong64-gnu": {
|
"node_modules/@rollup/rollup-linux-loong64-gnu": {
|
||||||
"version": "4.57.1",
|
"version": "4.59.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.57.1.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz",
|
||||||
"integrity": "sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==",
|
"integrity": "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"loong64"
|
"loong64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1814,13 +1782,12 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-loong64-musl": {
|
"node_modules/@rollup/rollup-linux-loong64-musl": {
|
||||||
"version": "4.57.1",
|
"version": "4.59.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.57.1.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz",
|
||||||
"integrity": "sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==",
|
"integrity": "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"loong64"
|
"loong64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1828,13 +1795,12 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-ppc64-gnu": {
|
"node_modules/@rollup/rollup-linux-ppc64-gnu": {
|
||||||
"version": "4.57.1",
|
"version": "4.59.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.57.1.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz",
|
||||||
"integrity": "sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==",
|
"integrity": "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ppc64"
|
"ppc64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1842,13 +1808,12 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-ppc64-musl": {
|
"node_modules/@rollup/rollup-linux-ppc64-musl": {
|
||||||
"version": "4.57.1",
|
"version": "4.59.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.57.1.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz",
|
||||||
"integrity": "sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==",
|
"integrity": "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ppc64"
|
"ppc64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1856,13 +1821,12 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
|
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
|
||||||
"version": "4.57.1",
|
"version": "4.59.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.57.1.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz",
|
||||||
"integrity": "sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==",
|
"integrity": "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"riscv64"
|
"riscv64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1870,13 +1834,12 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-riscv64-musl": {
|
"node_modules/@rollup/rollup-linux-riscv64-musl": {
|
||||||
"version": "4.57.1",
|
"version": "4.59.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.57.1.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz",
|
||||||
"integrity": "sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==",
|
"integrity": "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"riscv64"
|
"riscv64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1884,13 +1847,12 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-s390x-gnu": {
|
"node_modules/@rollup/rollup-linux-s390x-gnu": {
|
||||||
"version": "4.57.1",
|
"version": "4.59.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.57.1.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz",
|
||||||
"integrity": "sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==",
|
"integrity": "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"s390x"
|
"s390x"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1898,11 +1860,12 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-x64-gnu": {
|
"node_modules/@rollup/rollup-linux-x64-gnu": {
|
||||||
"version": "4.57.1",
|
"version": "4.59.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz",
|
||||||
|
"integrity": "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1910,13 +1873,12 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-x64-musl": {
|
"node_modules/@rollup/rollup-linux-x64-musl": {
|
||||||
"version": "4.57.1",
|
"version": "4.59.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.57.1.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz",
|
||||||
"integrity": "sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw==",
|
"integrity": "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1924,13 +1886,12 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-openbsd-x64": {
|
"node_modules/@rollup/rollup-openbsd-x64": {
|
||||||
"version": "4.57.1",
|
"version": "4.59.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.57.1.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz",
|
||||||
"integrity": "sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==",
|
"integrity": "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1938,13 +1899,12 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-openharmony-arm64": {
|
"node_modules/@rollup/rollup-openharmony-arm64": {
|
||||||
"version": "4.57.1",
|
"version": "4.59.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.57.1.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz",
|
||||||
"integrity": "sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ==",
|
"integrity": "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1952,13 +1912,12 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-win32-arm64-msvc": {
|
"node_modules/@rollup/rollup-win32-arm64-msvc": {
|
||||||
"version": "4.57.1",
|
"version": "4.59.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.57.1.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz",
|
||||||
"integrity": "sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ==",
|
"integrity": "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1966,13 +1925,12 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-win32-ia32-msvc": {
|
"node_modules/@rollup/rollup-win32-ia32-msvc": {
|
||||||
"version": "4.57.1",
|
"version": "4.59.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.57.1.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz",
|
||||||
"integrity": "sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew==",
|
"integrity": "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ia32"
|
"ia32"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1980,13 +1938,12 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-win32-x64-gnu": {
|
"node_modules/@rollup/rollup-win32-x64-gnu": {
|
||||||
"version": "4.57.1",
|
"version": "4.59.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.57.1.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz",
|
||||||
"integrity": "sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ==",
|
"integrity": "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1994,13 +1951,12 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-win32-x64-msvc": {
|
"node_modules/@rollup/rollup-win32-x64-msvc": {
|
||||||
"version": "4.57.1",
|
"version": "4.59.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.57.1.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz",
|
||||||
"integrity": "sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA==",
|
"integrity": "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -2350,8 +2306,9 @@
|
|||||||
},
|
},
|
||||||
"node_modules/@types/node": {
|
"node_modules/@types/node": {
|
||||||
"version": "24.5.2",
|
"version": "24.5.2",
|
||||||
"dev": true,
|
"devOptional": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"undici-types": "~7.12.0"
|
"undici-types": "~7.12.0"
|
||||||
}
|
}
|
||||||
@@ -2366,8 +2323,8 @@
|
|||||||
},
|
},
|
||||||
"node_modules/@types/react": {
|
"node_modules/@types/react": {
|
||||||
"version": "18.3.27",
|
"version": "18.3.27",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/prop-types": "*",
|
"@types/prop-types": "*",
|
||||||
"csstype": "^3.2.2"
|
"csstype": "^3.2.2"
|
||||||
@@ -2489,6 +2446,7 @@
|
|||||||
"version": "8.15.0",
|
"version": "8.15.0",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
"acorn": "bin/acorn"
|
"acorn": "bin/acorn"
|
||||||
},
|
},
|
||||||
@@ -2505,7 +2463,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/ajv": {
|
"node_modules/ajv": {
|
||||||
"version": "6.12.6",
|
"version": "6.14.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz",
|
||||||
|
"integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -2811,6 +2771,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"baseline-browser-mapping": "^2.9.0",
|
"baseline-browser-mapping": "^2.9.0",
|
||||||
"caniuse-lite": "^1.0.30001759",
|
"caniuse-lite": "^1.0.30001759",
|
||||||
@@ -3242,7 +3203,8 @@
|
|||||||
},
|
},
|
||||||
"node_modules/dayjs": {
|
"node_modules/dayjs": {
|
||||||
"version": "1.11.13",
|
"version": "1.11.13",
|
||||||
"license": "MIT"
|
"license": "MIT",
|
||||||
|
"peer": true
|
||||||
},
|
},
|
||||||
"node_modules/debug": {
|
"node_modules/debug": {
|
||||||
"version": "4.4.3",
|
"version": "4.4.3",
|
||||||
@@ -3566,7 +3528,6 @@
|
|||||||
},
|
},
|
||||||
"node_modules/esbuild": {
|
"node_modules/esbuild": {
|
||||||
"version": "0.25.12",
|
"version": "0.25.12",
|
||||||
"dev": true,
|
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"bin": {
|
"bin": {
|
||||||
@@ -3625,6 +3586,7 @@
|
|||||||
"version": "8.57.1",
|
"version": "8.57.1",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslint-community/eslint-utils": "^4.2.0",
|
"@eslint-community/eslint-utils": "^4.2.0",
|
||||||
"@eslint-community/regexpp": "^4.6.1",
|
"@eslint-community/regexpp": "^4.6.1",
|
||||||
@@ -3884,7 +3846,6 @@
|
|||||||
},
|
},
|
||||||
"node_modules/fdir": {
|
"node_modules/fdir": {
|
||||||
"version": "6.5.0",
|
"version": "6.5.0",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12.0.0"
|
"node": ">=12.0.0"
|
||||||
@@ -4018,7 +3979,6 @@
|
|||||||
"version": "2.3.3",
|
"version": "2.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
||||||
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
|
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
|
||||||
"dev": true,
|
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
@@ -4371,6 +4331,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.27.6"
|
"@babel/runtime": "^7.27.6"
|
||||||
},
|
},
|
||||||
@@ -5154,6 +5115,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/maplibre-gl/-/maplibre-gl-5.19.0.tgz",
|
"resolved": "https://registry.npmjs.org/maplibre-gl/-/maplibre-gl-5.19.0.tgz",
|
||||||
"integrity": "sha512-REhYUN8gNP3HlcIZS6QU2uy8iovl31cXsrNDkCcqWSQbCkcpdYLczqDz5PVIwNH42UQNyvukjes/RoHPDrOUmQ==",
|
"integrity": "sha512-REhYUN8gNP3HlcIZS6QU2uy8iovl31cXsrNDkCcqWSQbCkcpdYLczqDz5PVIwNH42UQNyvukjes/RoHPDrOUmQ==",
|
||||||
"license": "BSD-3-Clause",
|
"license": "BSD-3-Clause",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@mapbox/geojson-rewind": "^0.5.2",
|
"@mapbox/geojson-rewind": "^0.5.2",
|
||||||
"@mapbox/jsonlint-lines-primitives": "^2.0.2",
|
"@mapbox/jsonlint-lines-primitives": "^2.0.2",
|
||||||
@@ -5251,7 +5213,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/minimatch": {
|
"node_modules/minimatch": {
|
||||||
"version": "3.1.2",
|
"version": "3.1.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
|
||||||
|
"integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -5317,7 +5281,6 @@
|
|||||||
},
|
},
|
||||||
"node_modules/nanoid": {
|
"node_modules/nanoid": {
|
||||||
"version": "3.3.11",
|
"version": "3.3.11",
|
||||||
"dev": true,
|
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"type": "github",
|
"type": "github",
|
||||||
@@ -5650,7 +5613,6 @@
|
|||||||
},
|
},
|
||||||
"node_modules/postcss": {
|
"node_modules/postcss": {
|
||||||
"version": "8.5.6",
|
"version": "8.5.6",
|
||||||
"dev": true,
|
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"type": "opencollective",
|
"type": "opencollective",
|
||||||
@@ -5801,6 +5763,7 @@
|
|||||||
"node_modules/react-dom": {
|
"node_modules/react-dom": {
|
||||||
"version": "18.3.1",
|
"version": "18.3.1",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"loose-envify": "^1.1.0",
|
"loose-envify": "^1.1.0",
|
||||||
"scheduler": "^0.23.2"
|
"scheduler": "^0.23.2"
|
||||||
@@ -5812,6 +5775,7 @@
|
|||||||
"node_modules/react-hook-form": {
|
"node_modules/react-hook-form": {
|
||||||
"version": "7.71.1",
|
"version": "7.71.1",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.0.0"
|
"node": ">=18.0.0"
|
||||||
},
|
},
|
||||||
@@ -6011,7 +5975,8 @@
|
|||||||
},
|
},
|
||||||
"node_modules/redux": {
|
"node_modules/redux": {
|
||||||
"version": "5.0.1",
|
"version": "5.0.1",
|
||||||
"license": "MIT"
|
"license": "MIT",
|
||||||
|
"peer": true
|
||||||
},
|
},
|
||||||
"node_modules/redux-persist": {
|
"node_modules/redux-persist": {
|
||||||
"version": "6.0.0",
|
"version": "6.0.0",
|
||||||
@@ -6157,9 +6122,11 @@
|
|||||||
"license": "Unlicense"
|
"license": "Unlicense"
|
||||||
},
|
},
|
||||||
"node_modules/rollup": {
|
"node_modules/rollup": {
|
||||||
"version": "4.57.1",
|
"version": "4.59.0",
|
||||||
"dev": true,
|
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz",
|
||||||
|
"integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/estree": "1.0.8"
|
"@types/estree": "1.0.8"
|
||||||
},
|
},
|
||||||
@@ -6171,31 +6138,31 @@
|
|||||||
"npm": ">=8.0.0"
|
"npm": ">=8.0.0"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@rollup/rollup-android-arm-eabi": "4.57.1",
|
"@rollup/rollup-android-arm-eabi": "4.59.0",
|
||||||
"@rollup/rollup-android-arm64": "4.57.1",
|
"@rollup/rollup-android-arm64": "4.59.0",
|
||||||
"@rollup/rollup-darwin-arm64": "4.57.1",
|
"@rollup/rollup-darwin-arm64": "4.59.0",
|
||||||
"@rollup/rollup-darwin-x64": "4.57.1",
|
"@rollup/rollup-darwin-x64": "4.59.0",
|
||||||
"@rollup/rollup-freebsd-arm64": "4.57.1",
|
"@rollup/rollup-freebsd-arm64": "4.59.0",
|
||||||
"@rollup/rollup-freebsd-x64": "4.57.1",
|
"@rollup/rollup-freebsd-x64": "4.59.0",
|
||||||
"@rollup/rollup-linux-arm-gnueabihf": "4.57.1",
|
"@rollup/rollup-linux-arm-gnueabihf": "4.59.0",
|
||||||
"@rollup/rollup-linux-arm-musleabihf": "4.57.1",
|
"@rollup/rollup-linux-arm-musleabihf": "4.59.0",
|
||||||
"@rollup/rollup-linux-arm64-gnu": "4.57.1",
|
"@rollup/rollup-linux-arm64-gnu": "4.59.0",
|
||||||
"@rollup/rollup-linux-arm64-musl": "4.57.1",
|
"@rollup/rollup-linux-arm64-musl": "4.59.0",
|
||||||
"@rollup/rollup-linux-loong64-gnu": "4.57.1",
|
"@rollup/rollup-linux-loong64-gnu": "4.59.0",
|
||||||
"@rollup/rollup-linux-loong64-musl": "4.57.1",
|
"@rollup/rollup-linux-loong64-musl": "4.59.0",
|
||||||
"@rollup/rollup-linux-ppc64-gnu": "4.57.1",
|
"@rollup/rollup-linux-ppc64-gnu": "4.59.0",
|
||||||
"@rollup/rollup-linux-ppc64-musl": "4.57.1",
|
"@rollup/rollup-linux-ppc64-musl": "4.59.0",
|
||||||
"@rollup/rollup-linux-riscv64-gnu": "4.57.1",
|
"@rollup/rollup-linux-riscv64-gnu": "4.59.0",
|
||||||
"@rollup/rollup-linux-riscv64-musl": "4.57.1",
|
"@rollup/rollup-linux-riscv64-musl": "4.59.0",
|
||||||
"@rollup/rollup-linux-s390x-gnu": "4.57.1",
|
"@rollup/rollup-linux-s390x-gnu": "4.59.0",
|
||||||
"@rollup/rollup-linux-x64-gnu": "4.57.1",
|
"@rollup/rollup-linux-x64-gnu": "4.59.0",
|
||||||
"@rollup/rollup-linux-x64-musl": "4.57.1",
|
"@rollup/rollup-linux-x64-musl": "4.59.0",
|
||||||
"@rollup/rollup-openbsd-x64": "4.57.1",
|
"@rollup/rollup-openbsd-x64": "4.59.0",
|
||||||
"@rollup/rollup-openharmony-arm64": "4.57.1",
|
"@rollup/rollup-openharmony-arm64": "4.59.0",
|
||||||
"@rollup/rollup-win32-arm64-msvc": "4.57.1",
|
"@rollup/rollup-win32-arm64-msvc": "4.59.0",
|
||||||
"@rollup/rollup-win32-ia32-msvc": "4.57.1",
|
"@rollup/rollup-win32-ia32-msvc": "4.59.0",
|
||||||
"@rollup/rollup-win32-x64-gnu": "4.57.1",
|
"@rollup/rollup-win32-x64-gnu": "4.59.0",
|
||||||
"@rollup/rollup-win32-x64-msvc": "4.57.1",
|
"@rollup/rollup-win32-x64-msvc": "4.59.0",
|
||||||
"fsevents": "~2.3.2"
|
"fsevents": "~2.3.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -6530,7 +6497,6 @@
|
|||||||
},
|
},
|
||||||
"node_modules/source-map-js": {
|
"node_modules/source-map-js": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"dev": true,
|
|
||||||
"license": "BSD-3-Clause",
|
"license": "BSD-3-Clause",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
@@ -6818,7 +6784,6 @@
|
|||||||
},
|
},
|
||||||
"node_modules/tinyglobby": {
|
"node_modules/tinyglobby": {
|
||||||
"version": "0.2.15",
|
"version": "0.2.15",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"fdir": "^6.5.0",
|
"fdir": "^6.5.0",
|
||||||
@@ -6948,8 +6913,9 @@
|
|||||||
},
|
},
|
||||||
"node_modules/typescript": {
|
"node_modules/typescript": {
|
||||||
"version": "5.9.3",
|
"version": "5.9.3",
|
||||||
"dev": true,
|
"devOptional": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
|
"peer": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
"tsc": "bin/tsc",
|
"tsc": "bin/tsc",
|
||||||
"tsserver": "bin/tsserver"
|
"tsserver": "bin/tsserver"
|
||||||
@@ -6992,7 +6958,7 @@
|
|||||||
},
|
},
|
||||||
"node_modules/undici-types": {
|
"node_modules/undici-types": {
|
||||||
"version": "7.12.0",
|
"version": "7.12.0",
|
||||||
"dev": true,
|
"devOptional": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/union-value": {
|
"node_modules/union-value": {
|
||||||
@@ -7081,9 +7047,11 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/vite": {
|
"node_modules/vite": {
|
||||||
"version": "6.3.6",
|
"version": "6.4.1",
|
||||||
"dev": true,
|
"resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz",
|
||||||
|
"integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"esbuild": "^0.25.0",
|
"esbuild": "^0.25.0",
|
||||||
"fdir": "^6.4.4",
|
"fdir": "^6.4.4",
|
||||||
@@ -7366,7 +7334,7 @@
|
|||||||
"version": "2.8.2",
|
"version": "2.8.2",
|
||||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz",
|
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz",
|
||||||
"integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==",
|
"integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==",
|
||||||
"dev": true,
|
"devOptional": true,
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"bin": {
|
"bin": {
|
||||||
"yaml": "bin.mjs"
|
"yaml": "bin.mjs"
|
||||||
|
|||||||
+1
-1
@@ -66,6 +66,6 @@
|
|||||||
"lint-staged": "^16.2.7",
|
"lint-staged": "^16.2.7",
|
||||||
"prettier": "^3.3.3",
|
"prettier": "^3.3.3",
|
||||||
"typescript": "5.9.3",
|
"typescript": "5.9.3",
|
||||||
"vite": "6.3.6"
|
"vite": "6.4.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -32,7 +32,7 @@ export const HeatmapResponseTime = ({
|
|||||||
|
|
||||||
if (!checks || checks.length === 0) return null;
|
if (!checks || checks.length === 0) return null;
|
||||||
|
|
||||||
const latestChecks = checks.slice(-25).reverse();
|
const latestChecks = checks.slice(-25);
|
||||||
|
|
||||||
let data: Array<CheckSnapshot | PlaceholderCheck>;
|
let data: Array<CheckSnapshot | PlaceholderCheck>;
|
||||||
if (latestChecks.length !== 25) {
|
if (latestChecks.length !== 25) {
|
||||||
|
|||||||
@@ -40,8 +40,6 @@ export const useMonitorForm = ({
|
|||||||
matchMethod: data?.matchMethod || "",
|
matchMethod: data?.matchMethod || "",
|
||||||
expectedValue: data?.expectedValue || "",
|
expectedValue: data?.expectedValue || "",
|
||||||
jsonPath: data?.jsonPath || "",
|
jsonPath: data?.jsonPath || "",
|
||||||
basicAuthUser: data?.basicAuthUser || "",
|
|
||||||
basicAuthPassword: data?.basicAuthPassword || "",
|
|
||||||
};
|
};
|
||||||
break;
|
break;
|
||||||
case "ping":
|
case "ping":
|
||||||
@@ -115,8 +113,6 @@ export const useMonitorForm = ({
|
|||||||
matchMethod: "",
|
matchMethod: "",
|
||||||
expectedValue: "",
|
expectedValue: "",
|
||||||
jsonPath: "",
|
jsonPath: "",
|
||||||
basicAuthUser: "",
|
|
||||||
basicAuthPassword: "",
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -770,50 +770,6 @@ const CreateMonitorPage = () => {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{watchedType === "http" && (
|
|
||||||
<ConfigBox
|
|
||||||
title={t("pages.createMonitor.form.basicAuth.title")}
|
|
||||||
subtitle={t("pages.createMonitor.form.basicAuth.description")}
|
|
||||||
rightContent={
|
|
||||||
<Stack spacing={theme.spacing(LAYOUT.MD)}>
|
|
||||||
<Controller
|
|
||||||
name="basicAuthUser"
|
|
||||||
control={control}
|
|
||||||
render={({ field, fieldState }) => (
|
|
||||||
<TextField
|
|
||||||
{...field}
|
|
||||||
value={field.value ?? ""}
|
|
||||||
fieldLabel={t(
|
|
||||||
"pages.createMonitor.form.basicAuth.option.username.label"
|
|
||||||
)}
|
|
||||||
fullWidth
|
|
||||||
error={!!fieldState.error}
|
|
||||||
helperText={fieldState.error?.message ?? ""}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<Controller
|
|
||||||
name="basicAuthPassword"
|
|
||||||
control={control}
|
|
||||||
render={({ field, fieldState }) => (
|
|
||||||
<TextField
|
|
||||||
{...field}
|
|
||||||
value={field.value ?? ""}
|
|
||||||
type="password"
|
|
||||||
fieldLabel={t(
|
|
||||||
"pages.createMonitor.form.basicAuth.option.password.label"
|
|
||||||
)}
|
|
||||||
fullWidth
|
|
||||||
error={!!fieldState.error}
|
|
||||||
helperText={fieldState.error?.message ?? ""}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</Stack>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{watchedType === "http" && (
|
{watchedType === "http" && (
|
||||||
<ConfigBox
|
<ConfigBox
|
||||||
title={t("pages.createMonitor.form.advanced.title")}
|
title={t("pages.createMonitor.form.advanced.title")}
|
||||||
|
|||||||
@@ -44,8 +44,6 @@ export interface Monitor {
|
|||||||
jsonPath?: string;
|
jsonPath?: string;
|
||||||
expectedValue?: string;
|
expectedValue?: string;
|
||||||
matchMethod?: MonitorMatchMethod;
|
matchMethod?: MonitorMatchMethod;
|
||||||
basicAuthUser?: string;
|
|
||||||
basicAuthPassword?: string;
|
|
||||||
url: string;
|
url: string;
|
||||||
port?: number;
|
port?: number;
|
||||||
isActive: boolean;
|
isActive: boolean;
|
||||||
|
|||||||
@@ -38,8 +38,6 @@ const httpSchema = baseSchema.extend({
|
|||||||
matchMethod: z.enum(["equal", "include", "regex", ""]).optional(),
|
matchMethod: z.enum(["equal", "include", "regex", ""]).optional(),
|
||||||
expectedValue: z.string().optional(),
|
expectedValue: z.string().optional(),
|
||||||
jsonPath: z.string().optional(),
|
jsonPath: z.string().optional(),
|
||||||
basicAuthUser: z.string().optional(),
|
|
||||||
basicAuthPassword: z.string().optional(),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Ping monitor schema
|
// Ping monitor schema
|
||||||
|
|||||||
@@ -501,18 +501,6 @@
|
|||||||
"hardware": "Monitor CPU, memory, disk usage, and temperature for your infrastructure."
|
"hardware": "Monitor CPU, memory, disk usage, and temperature for your infrastructure."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"basicAuth": {
|
|
||||||
"description": "Configure HTTP basic authentication credentials for this monitor.",
|
|
||||||
"option": {
|
|
||||||
"username": {
|
|
||||||
"label": "Username"
|
|
||||||
},
|
|
||||||
"password": {
|
|
||||||
"label": "Password"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"title": "Basic authentication"
|
|
||||||
},
|
|
||||||
"ignoreTls": {
|
"ignoreTls": {
|
||||||
"description": "Configure TLS/SSL certificate validation for HTTPS connections.",
|
"description": "Configure TLS/SSL certificate validation for HTTPS connections.",
|
||||||
"option": {
|
"option": {
|
||||||
|
|||||||
Generated
+700
-687
File diff suppressed because it is too large.
Load diff
+2
-2
@@ -38,7 +38,7 @@
|
|||||||
"dotenv": "^16.4.5",
|
"dotenv": "^16.4.5",
|
||||||
"express": "^4.19.2",
|
"express": "^4.19.2",
|
||||||
"express-rate-limit": "8.0.1",
|
"express-rate-limit": "8.0.1",
|
||||||
"gamedig": "^5.3.1",
|
"gamedig": "5.2.0",
|
||||||
"got": "14.4.7",
|
"got": "14.4.7",
|
||||||
"handlebars": "^4.7.8",
|
"handlebars": "^4.7.8",
|
||||||
"helmet": "^8.0.0",
|
"helmet": "^8.0.0",
|
||||||
@@ -51,7 +51,7 @@
|
|||||||
"mjml": "^5.0.0-alpha.4",
|
"mjml": "^5.0.0-alpha.4",
|
||||||
"mongoose": "^8.3.3",
|
"mongoose": "^8.3.3",
|
||||||
"multer": "^1.4.5-lts.1",
|
"multer": "^1.4.5-lts.1",
|
||||||
"nodemailer": "^6.9.14",
|
"nodemailer": "8.0.1",
|
||||||
"ping": "0.4.4",
|
"ping": "0.4.4",
|
||||||
"sharp": "0.33.5",
|
"sharp": "0.33.5",
|
||||||
"ssl-checker": "2.0.10",
|
"ssl-checker": "2.0.10",
|
||||||
|
|||||||
+16
-2
@@ -74,8 +74,22 @@ export const createApp = ({
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
// Swagger UI
|
// Swagger UI — dynamically set server URL from request
|
||||||
app.use("/api-docs", swaggerUi.serve, swaggerUi.setup(openApiSpec));
|
app.use("/api-docs", swaggerUi.serve, (req: express.Request, res: express.Response, next: express.NextFunction) => {
|
||||||
|
const protocol = req.protocol;
|
||||||
|
const host = req.get("host");
|
||||||
|
const dynamicSpec = {
|
||||||
|
...openApiSpec,
|
||||||
|
servers: [
|
||||||
|
{
|
||||||
|
url: `${protocol}://${host}/api/v1`,
|
||||||
|
description: "Current Server",
|
||||||
|
},
|
||||||
|
...openApiSpec.servers,
|
||||||
|
],
|
||||||
|
};
|
||||||
|
swaggerUi.setup(dynamicSpec)(req, res, next);
|
||||||
|
});
|
||||||
|
|
||||||
app.use("/api/v1/health", (req, res) => {
|
app.use("/api/v1/health", (req, res) => {
|
||||||
res.json({
|
res.json({
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ import { NotificationMessageBuilder, INotificationMessageBuilder } from "../serv
|
|||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import got from "got";
|
import got from "got";
|
||||||
import ping from "ping";
|
import ping from "ping";
|
||||||
import https from "https";
|
|
||||||
import Docker from "dockerode";
|
import Docker from "dockerode";
|
||||||
import net from "net";
|
import net from "net";
|
||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
@@ -75,6 +74,15 @@ import {
|
|||||||
} from "@/repositories/index.js";
|
} from "@/repositories/index.js";
|
||||||
import { ILogger } from "@/utils/logger.js";
|
import { ILogger } from "@/utils/logger.js";
|
||||||
import { EnvConfig } from "@/service/system/settingsService.js";
|
import { EnvConfig } from "@/service/system/settingsService.js";
|
||||||
|
import { PingProvider } from "@/service/infrastructure/network/PingProvider.js";
|
||||||
|
import { HttpProvider } from "@/service/infrastructure/network/HttpProvider.js";
|
||||||
|
import { AdvancedMatcher } from "@/service/infrastructure/network/AdvancedMatcher.js";
|
||||||
|
import { PageSpeedProvider } from "@/service/infrastructure/network/PageSpeedProvider.js";
|
||||||
|
import { HardwareProvider } from "@/service/infrastructure/network/HardwareProvider.js";
|
||||||
|
import { DockerProvider } from "@/service/infrastructure/network/DockerProvider.js";
|
||||||
|
import { PortProvider } from "@/service/infrastructure/network/PortProvider.js";
|
||||||
|
import { GameProvider } from "@/service/infrastructure/network/GameProvider.js";
|
||||||
|
import { GrpcProvider } from "@/service/infrastructure/network/GrpcProvider.js";
|
||||||
|
|
||||||
export type InitializedServices = {
|
export type InitializedServices = {
|
||||||
settingsService: any;
|
settingsService: any;
|
||||||
@@ -144,21 +152,28 @@ export const initializeServices = async ({
|
|||||||
const teamsRepository = new MongoTeamsRepository();
|
const teamsRepository = new MongoTeamsRepository();
|
||||||
const maintenanceWindowsRepository = new MongoMaintenanceWindowsRepository();
|
const maintenanceWindowsRepository = new MongoMaintenanceWindowsRepository();
|
||||||
|
|
||||||
|
// Providers
|
||||||
|
|
||||||
|
const pingProvider = new PingProvider(ping);
|
||||||
|
const httpProvider = new HttpProvider(got, new AdvancedMatcher(jmespath));
|
||||||
|
const pageSpeedProvider = new PageSpeedProvider(httpProvider, settingsService, logger);
|
||||||
|
const hardwareProvider = new HardwareProvider(httpProvider);
|
||||||
|
const dockerProvider = new DockerProvider(logger, Docker);
|
||||||
|
const portProvider = new PortProvider(net);
|
||||||
|
const gameProvider = new GameProvider(logger, GameDig);
|
||||||
|
const grpcProvider = new GrpcProvider(grpc, protoLoader);
|
||||||
|
|
||||||
const networkService = new NetworkService(
|
const networkService = new NetworkService(
|
||||||
axios,
|
axios,
|
||||||
got,
|
|
||||||
https,
|
|
||||||
jmespath,
|
|
||||||
GameDig as unknown as {
|
|
||||||
query: (options: { type: string; host: string; port?: number }) => Promise<{ ping?: number } & { [key: string]: unknown }>;
|
|
||||||
},
|
|
||||||
ping,
|
|
||||||
logger,
|
logger,
|
||||||
Docker,
|
pingProvider,
|
||||||
net,
|
httpProvider,
|
||||||
settingsService,
|
pageSpeedProvider,
|
||||||
grpc,
|
hardwareProvider,
|
||||||
protoLoader
|
dockerProvider,
|
||||||
|
portProvider,
|
||||||
|
gameProvider,
|
||||||
|
grpcProvider
|
||||||
);
|
);
|
||||||
const emailService = new EmailService(settingsService, fs, path, compile, mjml2html, nodemailer, logger);
|
const emailService = new EmailService(settingsService, fs, path, compile, mjml2html, nodemailer, logger);
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,60 @@
|
|||||||
|
import { MonitorModel } from "../models/Monitor.js";
|
||||||
|
import { logger } from "@/utils/logger.js";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fix infrastructure monitors that had thresholds incorrectly set to 0 or 5
|
||||||
|
* due to the old default value. Updates them to 100 (disabled).
|
||||||
|
*/
|
||||||
|
export async function fixInfrastructureThresholds(): Promise<void> {
|
||||||
|
const SERVICE_NAME = "Migration:FixInfrastructureThresholds";
|
||||||
|
|
||||||
|
try {
|
||||||
|
logger.info({ service: SERVICE_NAME, message: "Starting infrastructure threshold fix" });
|
||||||
|
|
||||||
|
const thresholdFields = ["cpuAlertThreshold", "memoryAlertThreshold", "diskAlertThreshold", "tempAlertThreshold"];
|
||||||
|
|
||||||
|
// Find hardware monitors with any threshold set to 0 or 5
|
||||||
|
const filter = {
|
||||||
|
type: "hardware",
|
||||||
|
$or: thresholdFields.flatMap((field) => [{ [field]: 0 }, { [field]: 5 }]),
|
||||||
|
};
|
||||||
|
|
||||||
|
const monitors = await MonitorModel.find(filter);
|
||||||
|
|
||||||
|
if (monitors.length === 0) {
|
||||||
|
logger.info({ service: SERVICE_NAME, message: "No monitors with threshold value of 0 or 5 found" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info({
|
||||||
|
service: SERVICE_NAME,
|
||||||
|
message: `Found ${monitors.length} monitors with threshold value of 0 or 5`,
|
||||||
|
});
|
||||||
|
|
||||||
|
let updatedCount = 0;
|
||||||
|
|
||||||
|
for (const monitor of monitors) {
|
||||||
|
const update: Record<string, number> = {};
|
||||||
|
|
||||||
|
for (const field of thresholdFields) {
|
||||||
|
if ((monitor as any)[field] === 0 || (monitor as any)[field] === 5) {
|
||||||
|
update[field] = 100;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Object.keys(update).length > 0) {
|
||||||
|
await MonitorModel.updateOne({ _id: monitor._id }, { $set: update });
|
||||||
|
updatedCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info({
|
||||||
|
service: SERVICE_NAME,
|
||||||
|
message: `Fixed ${updatedCount} monitors — thresholds updated from 0/5 to 100`,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||||
|
logger.error({ service: SERVICE_NAME, message: `Error fixing thresholds: ${errorMessage}` });
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import { migrateStatusWindowThreshold } from "./0001_migrateStatusWindowThreshold.js";
|
import { migrateStatusWindowThreshold } from "./0001_migrateStatusWindowThreshold.js";
|
||||||
import { convertChecksToTimeSeries } from "./0002_convertChecksToTimeSeries.js";
|
import { convertChecksToTimeSeries } from "./0002_convertChecksToTimeSeries.js";
|
||||||
import { cleanupDuplicateMonitorStats } from "./0003_cleanupDuplicateMonitorStats.js";
|
import { cleanupDuplicateMonitorStats } from "./0003_cleanupDuplicateMonitorStats.js";
|
||||||
|
import { fixInfrastructureThresholds } from "./0004_fixInfrastructureThresholds.js";
|
||||||
import MigrationModel from "../models/Migration.js";
|
import MigrationModel from "../models/Migration.js";
|
||||||
|
|
||||||
type MigrationEntry = {
|
type MigrationEntry = {
|
||||||
@@ -12,6 +13,7 @@ const migrations: MigrationEntry[] = [
|
|||||||
{ name: "0001_migrateStatusWindowThreshold", execute: migrateStatusWindowThreshold },
|
{ name: "0001_migrateStatusWindowThreshold", execute: migrateStatusWindowThreshold },
|
||||||
{ name: "0002_convertChecksToTimeSeries", execute: convertChecksToTimeSeries },
|
{ name: "0002_convertChecksToTimeSeries", execute: convertChecksToTimeSeries },
|
||||||
{ name: "0003_cleanupDuplicateMonitorStats", execute: cleanupDuplicateMonitorStats },
|
{ name: "0003_cleanupDuplicateMonitorStats", execute: cleanupDuplicateMonitorStats },
|
||||||
|
{ name: "0004_fixInfrastructureThresholds", execute: fixInfrastructureThresholds },
|
||||||
];
|
];
|
||||||
|
|
||||||
const runMigrations = async (logger?: { info: Function; error: Function }) => {
|
const runMigrations = async (logger?: { info: Function; error: Function }) => {
|
||||||
|
|||||||
@@ -93,12 +93,6 @@ const MonitorSchema = new Schema<MonitorDocument>(
|
|||||||
type: String,
|
type: String,
|
||||||
enum: ["equal", "include", "regex", ""],
|
enum: ["equal", "include", "regex", ""],
|
||||||
},
|
},
|
||||||
basicAuthUser: {
|
|
||||||
type: String,
|
|
||||||
},
|
|
||||||
basicAuthPassword: {
|
|
||||||
type: String,
|
|
||||||
},
|
|
||||||
url: {
|
url: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true,
|
required: true,
|
||||||
|
|||||||
@@ -20,12 +20,19 @@ export interface IChecksRepository {
|
|||||||
monitorId: string,
|
monitorId: string,
|
||||||
sortOrder: string,
|
sortOrder: string,
|
||||||
dateRange: string,
|
dateRange: string,
|
||||||
filter: string,
|
filter: string | undefined,
|
||||||
page: number,
|
page: number,
|
||||||
rowsPerPage: number,
|
rowsPerPage: number,
|
||||||
status: boolean | undefined
|
status: boolean | undefined
|
||||||
): Promise<ChecksQueryResult>;
|
): Promise<ChecksQueryResult>;
|
||||||
findByTeamId(sortOrder: string, dateRange: string, filter: string, page: number, rowsPerPage: number, teamId: string): Promise<ChecksQueryResult>;
|
findByTeamId(
|
||||||
|
sortOrder: string,
|
||||||
|
dateRange: string,
|
||||||
|
filter: string | undefined,
|
||||||
|
page: number,
|
||||||
|
rowsPerPage: number,
|
||||||
|
teamId: string
|
||||||
|
): Promise<ChecksQueryResult>;
|
||||||
findLatestByMonitorIds(monitorIds: string[], options?: { limitPerMonitor?: number }): Promise<LatestChecksMap>;
|
findLatestByMonitorIds(monitorIds: string[], options?: { limitPerMonitor?: number }): Promise<LatestChecksMap>;
|
||||||
findByDateRangeAndMonitorId(
|
findByDateRangeAndMonitorId(
|
||||||
monitorId: string,
|
monitorId: string,
|
||||||
|
|||||||
@@ -231,7 +231,7 @@ class MongoChecksRepository implements IChecksRepository {
|
|||||||
monitorId: string,
|
monitorId: string,
|
||||||
sortOrder: string,
|
sortOrder: string,
|
||||||
dateRange: string,
|
dateRange: string,
|
||||||
filter: string,
|
filter: string | undefined,
|
||||||
page: number,
|
page: number,
|
||||||
rowsPerPage: number,
|
rowsPerPage: number,
|
||||||
status: boolean | undefined
|
status: boolean | undefined
|
||||||
|
|||||||
@@ -124,8 +124,13 @@ class CheckService implements ICheckService {
|
|||||||
await this.monitorsRepository.findById(monitorId, teamId);
|
await this.monitorsRepository.findById(monitorId, teamId);
|
||||||
|
|
||||||
const { sortOrder, dateRange, filter, page, rowsPerPage, status } = query;
|
const { sortOrder, dateRange, filter, page, rowsPerPage, status } = query;
|
||||||
if (!sortOrder || !dateRange || !filter || !page || !rowsPerPage) {
|
if (!sortOrder || !dateRange || !page || !rowsPerPage) {
|
||||||
throw new AppError({ message: "Missing required query parameters", service: SERVICE_NAME, method: "getChecksByMonitor", status: 400 });
|
throw new AppError({
|
||||||
|
message: `Missing required query parameters sortOrder: ${sortOrder}, dateRange: ${dateRange}, filter: ${filter}, page: ${page}, rowsPerPage: ${rowsPerPage}`,
|
||||||
|
service: SERVICE_NAME,
|
||||||
|
method: "getChecksByMonitor",
|
||||||
|
status: 400,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
const parsedStatus = typeof status === "undefined" ? status : ParseBoolean(status);
|
const parsedStatus = typeof status === "undefined" ? status : ParseBoolean(status);
|
||||||
const parsedPage = page ? parseInt(page) : 0;
|
const parsedPage = page ? parseInt(page) : 0;
|
||||||
@@ -138,7 +143,7 @@ class CheckService implements ICheckService {
|
|||||||
|
|
||||||
getChecksByTeam = async ({ teamId, query }: { teamId: string; query: Record<string, string> }) => {
|
getChecksByTeam = async ({ teamId, query }: { teamId: string; query: Record<string, string> }) => {
|
||||||
const { sortOrder, dateRange, filter, page, rowsPerPage } = query;
|
const { sortOrder, dateRange, filter, page, rowsPerPage } = query;
|
||||||
if (!sortOrder || !dateRange || !filter || !page || !rowsPerPage) {
|
if (!sortOrder || !dateRange || !page || !rowsPerPage) {
|
||||||
throw new AppError({ message: "Missing required query parameters", service: SERVICE_NAME, method: "getChecksByTeam", status: 400 });
|
throw new AppError({ message: "Missing required query parameters", service: SERVICE_NAME, method: "getChecksByTeam", status: 400 });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ class EmailService implements IEmailService {
|
|||||||
if (!mjml) {
|
if (!mjml) {
|
||||||
throw new Error(`Template ${template} not found`);
|
throw new Error(`Template ${template} not found`);
|
||||||
}
|
}
|
||||||
const html = this.mjml2html(mjml);
|
const html = await this.mjml2html(mjml);
|
||||||
return html.html;
|
return html.html;
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
this.logger.error({
|
this.logger.error({
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
import { Monitor } from "@/types/monitor.js";
|
||||||
|
import jmespath from "jmespath";
|
||||||
|
type JmesPath = typeof jmespath;
|
||||||
|
|
||||||
|
export class AdvancedMatcher {
|
||||||
|
constructor(private jmespath: JmesPath) {}
|
||||||
|
|
||||||
|
private compare(actual: unknown, expected: string, method?: string): boolean {
|
||||||
|
if (method === "equal") return String(actual) === expected;
|
||||||
|
if (method === "include") return String(actual).includes(expected);
|
||||||
|
if (method === "regex") return new RegExp(expected).test(String(actual));
|
||||||
|
return String(actual) === expected; // Default
|
||||||
|
}
|
||||||
|
|
||||||
|
validate<T>(payload: T, monitor: Monitor): { ok: boolean; message: string; extracted?: any } {
|
||||||
|
const { useAdvancedMatching, jsonPath, matchMethod, expectedValue } = monitor;
|
||||||
|
if (!useAdvancedMatching) return { ok: true, message: "Success" };
|
||||||
|
|
||||||
|
let dataToValidate = payload;
|
||||||
|
|
||||||
|
if (jsonPath) {
|
||||||
|
try {
|
||||||
|
dataToValidate = this.jmespath.search(payload, jsonPath);
|
||||||
|
} catch {
|
||||||
|
return { ok: false, message: "Error evaluating JSON path" };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (expectedValue) {
|
||||||
|
const ok = this.compare(dataToValidate, expectedValue, matchMethod);
|
||||||
|
return {
|
||||||
|
ok,
|
||||||
|
message: ok ? "Success" : "Expected value did not match",
|
||||||
|
extracted: dataToValidate,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const isFalsey = dataToValidate === false || dataToValidate === "false" || dataToValidate === undefined || dataToValidate === null;
|
||||||
|
return {
|
||||||
|
ok: !isFalsey,
|
||||||
|
message: !isFalsey ? "Success" : "Extracted value is falsey",
|
||||||
|
extracted: dataToValidate,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,160 @@
|
|||||||
|
import { IStatusProvider } from "@/service/infrastructure/network/IStatusProvider.js";
|
||||||
|
import { DockerStatusPayload, MonitorStatusResponse } from "@/types/network.js";
|
||||||
|
import { Monitor, MonitorType } from "@/types/monitor.js";
|
||||||
|
import { ILogger } from "@/utils/logger.js";
|
||||||
|
import { AppError } from "@/utils/AppError.js";
|
||||||
|
import Dockerode from "dockerode";
|
||||||
|
import { NETWORK_ERROR, timeRequest } from "@/service/infrastructure/network/utils.js";
|
||||||
|
|
||||||
|
type DockerodeType = typeof Dockerode;
|
||||||
|
|
||||||
|
const SERVICE_NAME = "DockerProvider";
|
||||||
|
|
||||||
|
export interface DockerError extends Error {
|
||||||
|
statusCode?: number;
|
||||||
|
reason?: string;
|
||||||
|
json?: { message?: string };
|
||||||
|
}
|
||||||
|
|
||||||
|
export class DockerProvider implements IStatusProvider<DockerStatusPayload> {
|
||||||
|
readonly type = "docker";
|
||||||
|
private docker: Dockerode;
|
||||||
|
constructor(
|
||||||
|
private logger: ILogger,
|
||||||
|
private DockerLib: DockerodeType
|
||||||
|
) {
|
||||||
|
this.docker = new this.DockerLib();
|
||||||
|
}
|
||||||
|
|
||||||
|
supports(type: MonitorType): boolean {
|
||||||
|
return type === "docker";
|
||||||
|
}
|
||||||
|
|
||||||
|
private isDockerError(error: unknown): error is DockerError {
|
||||||
|
return error instanceof Error && ("statusCode" in error || "reason" in error || "json" in error);
|
||||||
|
}
|
||||||
|
|
||||||
|
async handle(monitor: Monitor): Promise<MonitorStatusResponse<DockerStatusPayload>> {
|
||||||
|
const { url: containerInput } = monitor;
|
||||||
|
try {
|
||||||
|
if (!containerInput) {
|
||||||
|
throw new Error("Container name or ID is required for Docker monitor");
|
||||||
|
}
|
||||||
|
|
||||||
|
const containers = await this.docker.listContainers({ all: true });
|
||||||
|
const normalizedInput = containerInput.replace(/^\/+/, "").toLowerCase();
|
||||||
|
|
||||||
|
// Priority-based matching to avoid ambiguity:
|
||||||
|
// 1. Exact full ID match (64-char)
|
||||||
|
const exactIdMatch = containers.find((c) => c.Id.toLowerCase() === normalizedInput);
|
||||||
|
|
||||||
|
// 2. Exact container name match (case-insensitive)
|
||||||
|
const exactNameMatch = containers.find((c) =>
|
||||||
|
c.Names.some((name: string) => {
|
||||||
|
const cleanName = name.replace(/^\/+/, "").toLowerCase();
|
||||||
|
return cleanName === normalizedInput;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
// 3. Partial ID match (fallback for backwards compatibility)
|
||||||
|
const partialIdMatch = containers.find((c) => c.Id.toLowerCase().startsWith(normalizedInput));
|
||||||
|
|
||||||
|
// Select container based on priority
|
||||||
|
const targetContainer = exactIdMatch || exactNameMatch || partialIdMatch;
|
||||||
|
|
||||||
|
// Handle no match
|
||||||
|
if (!targetContainer) {
|
||||||
|
this.logger.warn({
|
||||||
|
message: `No container found for "${monitor.url}".`,
|
||||||
|
service: SERVICE_NAME,
|
||||||
|
method: "handle",
|
||||||
|
details: { url: monitor.url },
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
monitorId: monitor.id,
|
||||||
|
teamId: monitor.teamId,
|
||||||
|
type: monitor.type,
|
||||||
|
status: false,
|
||||||
|
code: 404,
|
||||||
|
message: "Docker container not found",
|
||||||
|
responseTime: 0,
|
||||||
|
payload: null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. Handle Ambiguity Check
|
||||||
|
const matchTypes: string[] = [];
|
||||||
|
if (exactIdMatch) matchTypes.push("exact ID");
|
||||||
|
if (exactNameMatch) matchTypes.push("exact name");
|
||||||
|
if (partialIdMatch && !exactIdMatch) matchTypes.push("partial ID");
|
||||||
|
|
||||||
|
if (matchTypes.length > 1) {
|
||||||
|
const message = `Ambiguous container match for "${containerInput}". Matched by: ${matchTypes.join(", ")}. Using ${exactIdMatch ? "exact ID" : exactNameMatch ? "exact name" : "partial ID"} match.`;
|
||||||
|
|
||||||
|
this.logger.warn({
|
||||||
|
message,
|
||||||
|
service: SERVICE_NAME,
|
||||||
|
method: "handle",
|
||||||
|
details: { url: containerInput },
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
monitorId: monitor.id,
|
||||||
|
teamId: monitor.teamId,
|
||||||
|
type: monitor.type,
|
||||||
|
status: false,
|
||||||
|
code: NETWORK_ERROR,
|
||||||
|
message,
|
||||||
|
responseTime: 0,
|
||||||
|
payload: null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6. Inspect Container Status
|
||||||
|
const container = this.docker.getContainer(targetContainer.Id);
|
||||||
|
const { response, responseTime, error } = await timeRequest(() => container.inspect());
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
let message = "Failed to fetch Docker container information";
|
||||||
|
let code = NETWORK_ERROR;
|
||||||
|
|
||||||
|
if (this.isDockerError(error)) {
|
||||||
|
code = error.statusCode ?? NETWORK_ERROR;
|
||||||
|
message = error.json?.message ?? error.reason ?? error.message;
|
||||||
|
} else if (error instanceof Error) {
|
||||||
|
message = error.message;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
monitorId: monitor.id,
|
||||||
|
teamId: monitor.teamId,
|
||||||
|
type: monitor.type,
|
||||||
|
status: false,
|
||||||
|
code: code,
|
||||||
|
message: message,
|
||||||
|
responseTime,
|
||||||
|
payload: null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
monitorId: monitor.id,
|
||||||
|
teamId: monitor.teamId,
|
||||||
|
type: monitor.type,
|
||||||
|
status: response?.State?.Status === "running",
|
||||||
|
code: 200,
|
||||||
|
message: "Docker container status fetched successfully",
|
||||||
|
responseTime,
|
||||||
|
payload: response as unknown as DockerStatusPayload,
|
||||||
|
};
|
||||||
|
} catch (err: unknown) {
|
||||||
|
throw new AppError({
|
||||||
|
message: err instanceof Error ? err.message : "Error performing Docker request",
|
||||||
|
service: SERVICE_NAME,
|
||||||
|
method: "handle",
|
||||||
|
details: { url: containerInput },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
import { IStatusProvider } from "@/service/infrastructure/network/IStatusProvider.js";
|
||||||
|
import { GameStatusPayload, MonitorStatusResponse } from "@/types/network.js";
|
||||||
|
import { Monitor, MonitorType } from "@/types/monitor.js";
|
||||||
|
import { GameDig } from "gamedig";
|
||||||
|
import { AppError } from "@/utils/AppError.js";
|
||||||
|
import { NETWORK_ERROR } from "@/service/infrastructure/network/utils.js";
|
||||||
|
import { ILogger } from "@/utils/logger.js";
|
||||||
|
|
||||||
|
type GameDigType = typeof GameDig;
|
||||||
|
|
||||||
|
const SERVICE_NAME = "GameProvider";
|
||||||
|
export class GameProvider implements IStatusProvider<GameStatusPayload> {
|
||||||
|
readonly type = "game";
|
||||||
|
constructor(
|
||||||
|
private logger: ILogger,
|
||||||
|
private gameDig: GameDigType
|
||||||
|
) {}
|
||||||
|
|
||||||
|
supports(type: MonitorType): boolean {
|
||||||
|
return type === "game";
|
||||||
|
}
|
||||||
|
|
||||||
|
async handle(monitor: Monitor): Promise<MonitorStatusResponse<GameStatusPayload>> {
|
||||||
|
const { url, port, gameId, id, teamId, type } = monitor;
|
||||||
|
|
||||||
|
// Clean the host in case a full URL was passed
|
||||||
|
const host = url?.replace(/^https?:\/\//, "").split(/[/?#:]/)[0] || "";
|
||||||
|
try {
|
||||||
|
const state = await this.gameDig
|
||||||
|
.query({
|
||||||
|
type: gameId ?? "unknown",
|
||||||
|
host: host,
|
||||||
|
port: port ?? 0,
|
||||||
|
})
|
||||||
|
.catch((error: unknown) => {
|
||||||
|
this.logger.warn({
|
||||||
|
message: error instanceof Error ? error.message : String(error),
|
||||||
|
service: SERVICE_NAME,
|
||||||
|
method: "handle",
|
||||||
|
details: { host, port, gameId },
|
||||||
|
});
|
||||||
|
return undefined;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!state) {
|
||||||
|
return {
|
||||||
|
monitorId: id,
|
||||||
|
teamId: teamId,
|
||||||
|
type: type,
|
||||||
|
status: false,
|
||||||
|
code: NETWORK_ERROR,
|
||||||
|
message: "No response from game server",
|
||||||
|
responseTime: 0,
|
||||||
|
payload: null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
monitorId: id,
|
||||||
|
teamId: teamId,
|
||||||
|
type: type,
|
||||||
|
status: true,
|
||||||
|
code: 200,
|
||||||
|
message: "Success",
|
||||||
|
responseTime: state.ping ?? 0,
|
||||||
|
payload: state,
|
||||||
|
};
|
||||||
|
} catch (err: unknown) {
|
||||||
|
const originalMessage = err instanceof Error ? err.message : String(err);
|
||||||
|
throw new AppError({
|
||||||
|
message: originalMessage || "Error performing game server check",
|
||||||
|
service: SERVICE_NAME,
|
||||||
|
method: "handle",
|
||||||
|
details: { url: monitor.url, port: monitor.port, gameId: monitor.gameId },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,180 @@
|
|||||||
|
import { IStatusProvider } from "@/service/infrastructure/network/IStatusProvider.js";
|
||||||
|
import { GrpcStatusPayload, MonitorStatusResponse } from "@/types/network.js";
|
||||||
|
import { Monitor, MonitorType } from "@/types/monitor.js";
|
||||||
|
import { AppError } from "@/utils/AppError.js";
|
||||||
|
import { timeRequest } from "@/service/infrastructure/network/utils.js";
|
||||||
|
import * as grpc from "@grpc/grpc-js";
|
||||||
|
import * as protoLoader from "@grpc/proto-loader";
|
||||||
|
|
||||||
|
import path from "path";
|
||||||
|
import { fileURLToPath } from "url";
|
||||||
|
|
||||||
|
type GrpcType = typeof grpc;
|
||||||
|
type ProtoLoaderType = typeof protoLoader;
|
||||||
|
|
||||||
|
const SERVICE_NAME = "GrpcProvider";
|
||||||
|
|
||||||
|
export class GrpcProvider implements IStatusProvider<GrpcStatusPayload> {
|
||||||
|
readonly type = "grpc";
|
||||||
|
constructor(
|
||||||
|
private grpc: GrpcType,
|
||||||
|
private protoLoader: ProtoLoaderType
|
||||||
|
) {}
|
||||||
|
|
||||||
|
supports(type: MonitorType): boolean {
|
||||||
|
return type === "grpc";
|
||||||
|
}
|
||||||
|
|
||||||
|
private getGrpcStatusName(code: number): string {
|
||||||
|
const statusNames: Record<number, string> = {
|
||||||
|
0: "OK",
|
||||||
|
1: "CANCELLED",
|
||||||
|
2: "UNKNOWN",
|
||||||
|
3: "INVALID_ARGUMENT",
|
||||||
|
4: "DEADLINE_EXCEEDED",
|
||||||
|
5: "NOT_FOUND",
|
||||||
|
6: "ALREADY_EXISTS",
|
||||||
|
7: "PERMISSION_DENIED",
|
||||||
|
8: "RESOURCE_EXHAUSTED",
|
||||||
|
9: "FAILED_PRECONDITION",
|
||||||
|
10: "ABORTED",
|
||||||
|
11: "OUT_OF_RANGE",
|
||||||
|
12: "UNIMPLEMENTED",
|
||||||
|
13: "INTERNAL",
|
||||||
|
14: "UNAVAILABLE",
|
||||||
|
15: "DATA_LOSS",
|
||||||
|
16: "UNAUTHENTICATED",
|
||||||
|
};
|
||||||
|
return statusNames[code] || "UNKNOWN";
|
||||||
|
}
|
||||||
|
|
||||||
|
async handle(monitor: Monitor): Promise<MonitorStatusResponse<GrpcStatusPayload>> {
|
||||||
|
try {
|
||||||
|
const { id, teamId, type, url, port, ignoreTlsErrors } = monitor;
|
||||||
|
const grpcServiceName = monitor.grpcServiceName || "";
|
||||||
|
|
||||||
|
if (!url) {
|
||||||
|
throw new AppError({ message: "Monitor host is required", service: SERVICE_NAME, method: "handle" });
|
||||||
|
}
|
||||||
|
if (!port) {
|
||||||
|
throw new AppError({ message: "Monitor port is required", service: SERVICE_NAME, method: "handle" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const host = url?.replace(/^https?:\/\//, "").split(/[/?#:]/)[0];
|
||||||
|
const target = `${host}:${port}`;
|
||||||
|
|
||||||
|
const currentFilePath = fileURLToPath(import.meta.url);
|
||||||
|
const protoPath = path.join(path.dirname(currentFilePath), "protos", "health.proto");
|
||||||
|
const packageDefinition = this.protoLoader.loadSync(protoPath, {
|
||||||
|
keepCase: true,
|
||||||
|
longs: String,
|
||||||
|
enums: String,
|
||||||
|
defaults: true,
|
||||||
|
oneofs: true,
|
||||||
|
});
|
||||||
|
const grpcObject = this.grpc.loadPackageDefinition(packageDefinition) as unknown as {
|
||||||
|
grpc: {
|
||||||
|
health: {
|
||||||
|
v1: {
|
||||||
|
Health: new (
|
||||||
|
target: string,
|
||||||
|
credentials: unknown
|
||||||
|
) => {
|
||||||
|
Check: (request: { service: string }, options: { deadline: Date }, callback: (err: unknown, response: unknown) => void) => void;
|
||||||
|
close: () => void;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
const healthService = grpcObject.grpc.health.v1.Health;
|
||||||
|
|
||||||
|
let credentials;
|
||||||
|
if (ignoreTlsErrors) {
|
||||||
|
credentials = this.grpc.credentials.createSsl(null, null, null, {
|
||||||
|
checkServerIdentity: () => undefined,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
credentials = this.grpc.credentials.createInsecure();
|
||||||
|
}
|
||||||
|
|
||||||
|
const client = new healthService(target, credentials);
|
||||||
|
|
||||||
|
const TIMEOUT_MS = 10000;
|
||||||
|
const deadline = new Date(Date.now() + TIMEOUT_MS);
|
||||||
|
|
||||||
|
const { response, responseTime, error } = await timeRequest<GrpcStatusPayload>(() => {
|
||||||
|
return new Promise<GrpcStatusPayload>((resolve, reject) => {
|
||||||
|
client.Check({ service: grpcServiceName }, { deadline }, (err: unknown, response: unknown) => {
|
||||||
|
client.close();
|
||||||
|
|
||||||
|
if (err) {
|
||||||
|
const grpcErr = err as { code?: number; details?: string; message?: string };
|
||||||
|
const payload: GrpcStatusPayload = {
|
||||||
|
grpcStatusCode: grpcErr.code ?? -1,
|
||||||
|
grpcStatusName: this.getGrpcStatusName(grpcErr.code ?? -1),
|
||||||
|
serviceName: grpcServiceName,
|
||||||
|
servingStatus: "UNKNOWN",
|
||||||
|
};
|
||||||
|
const grpcError = new AppError({
|
||||||
|
message: grpcErr.details || grpcErr.message || "gRPC error",
|
||||||
|
service: SERVICE_NAME,
|
||||||
|
method: "handle",
|
||||||
|
}) as AppError & { grpcPayload?: GrpcStatusPayload; grpcCode?: number };
|
||||||
|
grpcError.grpcPayload = payload;
|
||||||
|
grpcError.grpcCode = grpcErr.code;
|
||||||
|
reject(grpcError);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const resp = response as { status?: string } | undefined;
|
||||||
|
const servingStatus = resp?.status ?? "UNKNOWN";
|
||||||
|
resolve({
|
||||||
|
grpcStatusCode: 0,
|
||||||
|
grpcStatusName: "OK",
|
||||||
|
serviceName: grpcServiceName,
|
||||||
|
servingStatus,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
const grpcError = error as AppError & { grpcPayload?: GrpcStatusPayload; grpcCode?: number };
|
||||||
|
const payload = grpcError.grpcPayload;
|
||||||
|
return {
|
||||||
|
monitorId: id,
|
||||||
|
teamId: teamId,
|
||||||
|
type: type,
|
||||||
|
status: false,
|
||||||
|
code: grpcError.grpcCode ?? 5000,
|
||||||
|
message: grpcError.message ?? "gRPC health check failed",
|
||||||
|
responseTime,
|
||||||
|
payload: payload ?? null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const grpcPayload = response as GrpcStatusPayload;
|
||||||
|
const isServing = grpcPayload.servingStatus === "SERVING";
|
||||||
|
|
||||||
|
return {
|
||||||
|
monitorId: id,
|
||||||
|
teamId: teamId,
|
||||||
|
type: type,
|
||||||
|
status: isServing,
|
||||||
|
code: isServing ? 200 : 5000,
|
||||||
|
message: isServing ? `gRPC service healthy (${grpcPayload.servingStatus})` : `gRPC service unhealthy (${grpcPayload.servingStatus})`,
|
||||||
|
responseTime,
|
||||||
|
payload: grpcPayload,
|
||||||
|
};
|
||||||
|
} catch (err: unknown) {
|
||||||
|
const originalMessage = err instanceof Error ? err.message : String(err);
|
||||||
|
throw new AppError({
|
||||||
|
message: originalMessage || "Error performing gRPC health check",
|
||||||
|
service: SERVICE_NAME,
|
||||||
|
method: "handle",
|
||||||
|
details: { url: monitor.url, port: monitor.port, grpcServiceName: monitor.grpcServiceName },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import { IStatusProvider } from "@/service/infrastructure/network/IStatusProvider.js";
|
||||||
|
import { HardwareStatusPayload, MonitorStatusResponse } from "@/types/network.js";
|
||||||
|
import { Monitor, MonitorType } from "@/types/monitor.js";
|
||||||
|
import { HttpProvider } from "@/service/infrastructure/network/HttpProvider.js";
|
||||||
|
import { AppError } from "@/utils/AppError.js";
|
||||||
|
|
||||||
|
export class HardwareProvider implements IStatusProvider<HardwareStatusPayload> {
|
||||||
|
readonly type = "hardware";
|
||||||
|
constructor(private httpProvider: HttpProvider) {}
|
||||||
|
|
||||||
|
supports(type: MonitorType) {
|
||||||
|
return type === "hardware";
|
||||||
|
}
|
||||||
|
|
||||||
|
async handle(monitor: Monitor): Promise<MonitorStatusResponse<HardwareStatusPayload>> {
|
||||||
|
const { url } = monitor;
|
||||||
|
try {
|
||||||
|
if (!url) throw new Error("URL is required for Hardware monitor");
|
||||||
|
return await this.httpProvider.handle<HardwareStatusPayload>(monitor);
|
||||||
|
} catch (err: unknown) {
|
||||||
|
throw new AppError({
|
||||||
|
message: err instanceof Error ? err.message : "Error performing Hardware request",
|
||||||
|
service: "HardwareProvider",
|
||||||
|
method: "handle",
|
||||||
|
details: { url: monitor.url },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,121 @@
|
|||||||
|
import { type Got, HTTPError, RequestError } from "got";
|
||||||
|
import { AdvancedMatcher } from "@/service/infrastructure/network/AdvancedMatcher.js";
|
||||||
|
import { IStatusProvider } from "@/service/infrastructure/network/IStatusProvider.js";
|
||||||
|
import { HttpStatusPayload } from "@/types/network.js";
|
||||||
|
import { MonitorStatusResponse } from "@/types/network.js";
|
||||||
|
import { Agent as HttpsAgent } from "https";
|
||||||
|
import { Monitor, MonitorType } from "@/types/monitor.js";
|
||||||
|
import { NETWORK_ERROR } from "@/service/infrastructure/network/utils.js";
|
||||||
|
import CacheableLookup from "cacheable-lookup";
|
||||||
|
|
||||||
|
export class HttpProvider implements IStatusProvider<HttpStatusPayload> {
|
||||||
|
readonly type = "http";
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private got: Got,
|
||||||
|
private advancedMatcher: AdvancedMatcher
|
||||||
|
) {
|
||||||
|
const cacheable = new CacheableLookup();
|
||||||
|
this.got = got.extend({
|
||||||
|
dnsCache: cacheable,
|
||||||
|
timeout: {
|
||||||
|
request: 30000,
|
||||||
|
},
|
||||||
|
retry: { limit: 1 },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
supports(type: MonitorType) {
|
||||||
|
return type === "http";
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleHttpError<T>(error: unknown, monitor: Monitor): MonitorStatusResponse<T> {
|
||||||
|
if (error instanceof HTTPError || error instanceof RequestError) {
|
||||||
|
return {
|
||||||
|
monitorId: monitor.id,
|
||||||
|
teamId: monitor.teamId,
|
||||||
|
type: monitor.type,
|
||||||
|
status: false,
|
||||||
|
code: error.response?.statusCode ?? NETWORK_ERROR,
|
||||||
|
message: error.message,
|
||||||
|
responseTime: error.timings?.phases?.total ?? 0,
|
||||||
|
timings: error.timings,
|
||||||
|
payload: null as T,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
monitorId: monitor.id,
|
||||||
|
teamId: monitor.teamId,
|
||||||
|
type: monitor.type,
|
||||||
|
status: false,
|
||||||
|
code: NETWORK_ERROR,
|
||||||
|
message: error instanceof Error ? error.message : String(error),
|
||||||
|
responseTime: 0,
|
||||||
|
payload: null as T,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async handle<T>(monitor: Monitor): Promise<MonitorStatusResponse<T>> {
|
||||||
|
const { url, secret, jsonPath, ignoreTlsErrors } = monitor;
|
||||||
|
|
||||||
|
if (!url) {
|
||||||
|
throw new Error("URL is required for HTTP monitor");
|
||||||
|
}
|
||||||
|
|
||||||
|
const options: Record<string, unknown> = {
|
||||||
|
headers: monitor.secret ? { Authorization: `Bearer ${secret}` } : undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
options.agent = {
|
||||||
|
https: new HttpsAgent({ rejectUnauthorized: !ignoreTlsErrors }),
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await this.got<string>(url, options);
|
||||||
|
const contentType = response.headers["content-type"] || "";
|
||||||
|
const isJson = contentType.includes("application/json");
|
||||||
|
|
||||||
|
if (jsonPath && !isJson) {
|
||||||
|
return {
|
||||||
|
monitorId: monitor.id,
|
||||||
|
teamId: monitor.teamId,
|
||||||
|
type: monitor.type,
|
||||||
|
status: false,
|
||||||
|
code: response.statusCode,
|
||||||
|
message: "Response is not JSON",
|
||||||
|
responseTime: response.timings.phases.total ?? 0,
|
||||||
|
timings: response.timings,
|
||||||
|
payload: response.body as unknown as T,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let payload: T;
|
||||||
|
if (isJson) {
|
||||||
|
try {
|
||||||
|
payload = JSON.parse(response.body) as T;
|
||||||
|
} catch {
|
||||||
|
payload = response.body as unknown as T;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
payload = response.body as unknown as T;
|
||||||
|
}
|
||||||
|
|
||||||
|
const matchResult = this.advancedMatcher.validate<T>(payload, monitor);
|
||||||
|
return {
|
||||||
|
monitorId: monitor.id,
|
||||||
|
teamId: monitor.teamId,
|
||||||
|
type: monitor.type,
|
||||||
|
status: response.ok && matchResult.ok,
|
||||||
|
code: response.statusCode,
|
||||||
|
message: matchResult.ok ? (response.statusMessage ?? "OK") : matchResult.message,
|
||||||
|
responseTime: response.timings.phases.total ?? 0,
|
||||||
|
timings: response.timings,
|
||||||
|
payload,
|
||||||
|
extracted: matchResult.extracted,
|
||||||
|
};
|
||||||
|
} catch (error: unknown) {
|
||||||
|
return this.handleHttpError(error, monitor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import { Monitor, MonitorType } from "@/types/monitor.js";
|
||||||
|
import { MonitorStatusResponse } from "@/types/network.js";
|
||||||
|
|
||||||
|
export interface IStatusProvider<T> {
|
||||||
|
type: string;
|
||||||
|
supports: (type: MonitorType) => boolean;
|
||||||
|
handle(monitor: Monitor): Promise<MonitorStatusResponse<T>>;
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
import { IStatusProvider } from "@/service/infrastructure/network/IStatusProvider.js";
|
||||||
|
import { MonitorStatusResponse, PageSpeedStatusPayload } from "@/types/network.js";
|
||||||
|
import { Monitor, MonitorType } from "@/types/monitor.js";
|
||||||
|
import { HttpProvider } from "@/service/infrastructure/network/HttpProvider.js";
|
||||||
|
import { ISettingsService } from "@/service/system/settingsService.js";
|
||||||
|
import { ILogger } from "@/utils/logger.js";
|
||||||
|
import { AppError } from "@/utils/AppError.js";
|
||||||
|
|
||||||
|
export class PageSpeedProvider implements IStatusProvider<PageSpeedStatusPayload> {
|
||||||
|
readonly type = "pagespeed";
|
||||||
|
constructor(
|
||||||
|
private httpProvider: HttpProvider,
|
||||||
|
private settingsService: ISettingsService,
|
||||||
|
private logger: ILogger
|
||||||
|
) {}
|
||||||
|
|
||||||
|
supports(type: MonitorType) {
|
||||||
|
return type === "pagespeed";
|
||||||
|
}
|
||||||
|
|
||||||
|
async handle(monitor: Monitor): Promise<MonitorStatusResponse<PageSpeedStatusPayload>> {
|
||||||
|
const { url } = monitor;
|
||||||
|
try {
|
||||||
|
if (!url) throw new Error("URL is required for PageSpeed monitor");
|
||||||
|
const dbSettings = await this.settingsService.getDBSettings();
|
||||||
|
const apiKey = dbSettings?.pagespeedApiKey;
|
||||||
|
let pageSpeedUrl = `https://pagespeedonline.googleapis.com/pagespeedonline/v5/runPagespeed?url=${encodeURIComponent(
|
||||||
|
url
|
||||||
|
)}&category=seo&category=accessibility&category=best-practices&category=performance`;
|
||||||
|
|
||||||
|
if (apiKey) {
|
||||||
|
pageSpeedUrl += `&key=${apiKey}`;
|
||||||
|
} else {
|
||||||
|
this.logger.warn({
|
||||||
|
message: "PageSpeed API key not found, performance may be throttled",
|
||||||
|
service: "PageSpeedProvider",
|
||||||
|
method: "handle",
|
||||||
|
details: { url },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return await this.httpProvider.handle<PageSpeedStatusPayload>({
|
||||||
|
...monitor,
|
||||||
|
url: pageSpeedUrl,
|
||||||
|
});
|
||||||
|
} catch (err: unknown) {
|
||||||
|
throw new AppError({
|
||||||
|
message: err instanceof Error ? err.message : "Error performing PageSpeed request",
|
||||||
|
service: "PageSpeedProvider",
|
||||||
|
method: "handle",
|
||||||
|
details: { url: monitor.url },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
import { PingStatusPayload } from "@/types/network.js";
|
||||||
|
import { IStatusProvider } from "./IStatusProvider.js";
|
||||||
|
import { MonitorType, Monitor } from "@/types/monitor.js";
|
||||||
|
import { MonitorStatusResponse } from "@/types/network.js";
|
||||||
|
import { AppError } from "@/utils/AppError.js";
|
||||||
|
import ping from "ping";
|
||||||
|
import { buildStatusResponse, timeRequest } from "@/service/infrastructure/network/utils.js";
|
||||||
|
const SERVICE_NAME = "PingProvider";
|
||||||
|
|
||||||
|
type Ping = typeof ping;
|
||||||
|
|
||||||
|
export class PingProvider implements IStatusProvider<PingStatusPayload> {
|
||||||
|
readonly type = "ping";
|
||||||
|
|
||||||
|
constructor(private ping: Ping) {}
|
||||||
|
|
||||||
|
supports(type: MonitorType): boolean {
|
||||||
|
return type === "ping";
|
||||||
|
}
|
||||||
|
|
||||||
|
private sanitizeHost(url: string): string {
|
||||||
|
return url
|
||||||
|
.replace(/^https?:\/\//, "")
|
||||||
|
.replace(/\/.*$/, "")
|
||||||
|
.replace(/:.*/, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
async handle(monitor: Monitor): Promise<MonitorStatusResponse<PingStatusPayload>> {
|
||||||
|
try {
|
||||||
|
if (!monitor.url) {
|
||||||
|
throw new Error("URL is required for ping monitor");
|
||||||
|
}
|
||||||
|
|
||||||
|
const sanitizedHost = this.sanitizeHost(monitor.url);
|
||||||
|
const { response, error } = await timeRequest<PingStatusPayload>(() => this.ping.promise.probe(sanitizedHost));
|
||||||
|
const safeTime = typeof response?.time === "number" ? response.time : parseFloat(String(response?.time)) || 0;
|
||||||
|
if (error) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!response) {
|
||||||
|
throw new Error(`No response from ping for host: ${sanitizedHost}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return buildStatusResponse<PingStatusPayload>({
|
||||||
|
monitor,
|
||||||
|
payload: response,
|
||||||
|
overrides: {
|
||||||
|
status: response.alive ?? false,
|
||||||
|
code: 200,
|
||||||
|
message: "Success",
|
||||||
|
responseTime: safeTime,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} catch (err: unknown) {
|
||||||
|
const message = err instanceof Error ? err.message : String(err);
|
||||||
|
throw new AppError({
|
||||||
|
message,
|
||||||
|
service: SERVICE_NAME,
|
||||||
|
method: "handle",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
import { IStatusProvider } from "@/service/infrastructure/network/IStatusProvider.js";
|
||||||
|
import { PortStatusPayload, MonitorStatusResponse } from "@/types/network.js";
|
||||||
|
import { Monitor, MonitorType } from "@/types/monitor.js";
|
||||||
|
import { AppError } from "@/utils/AppError.js";
|
||||||
|
import { NETWORK_ERROR, timeRequest } from "@/service/infrastructure/network/utils.js";
|
||||||
|
import * as net from "net";
|
||||||
|
type NetType = typeof net;
|
||||||
|
|
||||||
|
const SERVICE_NAME = "PortProvider";
|
||||||
|
|
||||||
|
export class PortProvider implements IStatusProvider<PortStatusPayload> {
|
||||||
|
readonly type = "port";
|
||||||
|
|
||||||
|
constructor(private net: NetType) {}
|
||||||
|
|
||||||
|
supports(type: MonitorType): boolean {
|
||||||
|
return type === "port";
|
||||||
|
}
|
||||||
|
|
||||||
|
async handle(monitor: Monitor): Promise<MonitorStatusResponse<PortStatusPayload>> {
|
||||||
|
try {
|
||||||
|
const { url, port } = monitor;
|
||||||
|
if (!url || !port) {
|
||||||
|
throw new Error("URL and port are required for port monitoring");
|
||||||
|
}
|
||||||
|
|
||||||
|
const { responseTime, error } = await timeRequest(async () => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const socket = this.net.createConnection(
|
||||||
|
{
|
||||||
|
host: url,
|
||||||
|
port,
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
socket.end();
|
||||||
|
socket.destroy();
|
||||||
|
resolve({ success: true });
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
socket.setTimeout(5000);
|
||||||
|
socket.on("timeout", () => {
|
||||||
|
socket.destroy();
|
||||||
|
reject(new Error("Connection timeout"));
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on("error", (err: unknown) => {
|
||||||
|
socket.destroy();
|
||||||
|
reject(err);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
if (error) {
|
||||||
|
console.log(error);
|
||||||
|
const errorMessage = error instanceof Error ? error.message : "Port check failed";
|
||||||
|
return {
|
||||||
|
monitorId: monitor.id,
|
||||||
|
teamId: monitor.teamId,
|
||||||
|
type: monitor.type,
|
||||||
|
status: false,
|
||||||
|
code: NETWORK_ERROR,
|
||||||
|
message: errorMessage,
|
||||||
|
responseTime: responseTime,
|
||||||
|
timings: undefined,
|
||||||
|
payload: { success: false },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
monitorId: monitor.id,
|
||||||
|
teamId: monitor.teamId,
|
||||||
|
type: monitor.type,
|
||||||
|
status: true,
|
||||||
|
code: 200,
|
||||||
|
message: "Port check successful",
|
||||||
|
responseTime: responseTime,
|
||||||
|
timings: undefined,
|
||||||
|
payload: { success: true },
|
||||||
|
};
|
||||||
|
} catch (err: unknown) {
|
||||||
|
const originalMessage = err instanceof Error ? err.message : String(err);
|
||||||
|
throw new AppError({
|
||||||
|
message: originalMessage || "Error performing port check",
|
||||||
|
status: 500,
|
||||||
|
service: SERVICE_NAME,
|
||||||
|
method: "handle",
|
||||||
|
details: { url: monitor.url, port: monitor.port },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
import { HTTPError, RequestError } from "got";
|
||||||
|
import type { Response } from "got";
|
||||||
|
import type { Monitor } from "@/types/monitor.js";
|
||||||
|
import { MonitorStatusResponse, MonitorStatusResponseOverrides } from "@/types/network.js";
|
||||||
|
|
||||||
|
export const timeRequest = async <T>(operation: () => Promise<T>): Promise<{ response: T | null; responseTime: number; error: unknown }> => {
|
||||||
|
const start = process.hrtime.bigint();
|
||||||
|
try {
|
||||||
|
const response = await operation();
|
||||||
|
const elapsedMs = Math.round(Number(process.hrtime.bigint() - start) / 1_000_000);
|
||||||
|
return { response, responseTime: elapsedMs, error: null };
|
||||||
|
} catch (error) {
|
||||||
|
const elapsedMs = Math.round(Number(process.hrtime.bigint() - start) / 1_000_000);
|
||||||
|
return { response: null, responseTime: elapsedMs, error };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const NETWORK_ERROR = 5000;
|
||||||
|
export const PING_ERROR = 5001;
|
||||||
|
|
||||||
|
interface BuildStatusResponseArgs<T> {
|
||||||
|
monitor: Monitor;
|
||||||
|
response?: Response<T> | null;
|
||||||
|
error?: Error | RequestError | HTTPError | null;
|
||||||
|
payload?: T | null;
|
||||||
|
jsonPath?: string;
|
||||||
|
matchMethod?: MonitorStatusResponse["matchMethod"];
|
||||||
|
expectedValue?: string;
|
||||||
|
extracted?: unknown;
|
||||||
|
overrides?: MonitorStatusResponseOverrides<T>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const buildStatusResponse = <T>({
|
||||||
|
monitor,
|
||||||
|
response,
|
||||||
|
error,
|
||||||
|
payload,
|
||||||
|
jsonPath,
|
||||||
|
matchMethod,
|
||||||
|
expectedValue,
|
||||||
|
extracted,
|
||||||
|
overrides,
|
||||||
|
}: BuildStatusResponseArgs<T>): MonitorStatusResponse<T> => {
|
||||||
|
if (error) {
|
||||||
|
const statusResponse: MonitorStatusResponse<T> = {
|
||||||
|
monitorId: monitor.id,
|
||||||
|
teamId: monitor.teamId,
|
||||||
|
type: monitor.type,
|
||||||
|
status: false,
|
||||||
|
code: NETWORK_ERROR,
|
||||||
|
message: error.message ?? "Network error",
|
||||||
|
responseTime: 0,
|
||||||
|
timings: undefined,
|
||||||
|
jsonPath,
|
||||||
|
matchMethod,
|
||||||
|
expectedValue,
|
||||||
|
extracted,
|
||||||
|
payload,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (error instanceof HTTPError || error instanceof RequestError) {
|
||||||
|
statusResponse.code = error?.response?.statusCode ?? NETWORK_ERROR;
|
||||||
|
statusResponse.message = error.message;
|
||||||
|
statusResponse.responseTime = error.timings?.phases?.total ?? 0;
|
||||||
|
statusResponse.timings = error.timings;
|
||||||
|
}
|
||||||
|
return { ...statusResponse, ...(overrides ?? {}) };
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
monitorId: monitor.id,
|
||||||
|
teamId: monitor.teamId,
|
||||||
|
type: monitor.type,
|
||||||
|
status: response?.ok ?? false,
|
||||||
|
code: response?.statusCode ?? NETWORK_ERROR,
|
||||||
|
message: response?.statusMessage ?? "",
|
||||||
|
responseTime: response?.timings?.phases?.total ?? 0,
|
||||||
|
timings: response?.timings,
|
||||||
|
payload: payload ?? response?.body,
|
||||||
|
jsonPath,
|
||||||
|
matchMethod,
|
||||||
|
expectedValue,
|
||||||
|
extracted,
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -1,5 +1,3 @@
|
|||||||
import { HTTPError, RequestError } from "got";
|
|
||||||
import type { Got, Response } from "got";
|
|
||||||
import type {
|
import type {
|
||||||
Monitor,
|
Monitor,
|
||||||
MonitorStatusResponse,
|
MonitorStatusResponse,
|
||||||
@@ -14,28 +12,11 @@ import type {
|
|||||||
} from "@/types/index.js";
|
} from "@/types/index.js";
|
||||||
import type { AxiosStatic } from "axios";
|
import type { AxiosStatic } from "axios";
|
||||||
import { AppError } from "@/utils/AppError.js";
|
import { AppError } from "@/utils/AppError.js";
|
||||||
import path from "path";
|
|
||||||
import { fileURLToPath } from "url";
|
|
||||||
|
|
||||||
import CacheableLookup from "cacheable-lookup";
|
|
||||||
import { ISettingsService } from "../system/settingsService.js";
|
|
||||||
import { ILogger } from "@/utils/logger.js";
|
import { ILogger } from "@/utils/logger.js";
|
||||||
|
import { IStatusProvider } from "./network/IStatusProvider.js";
|
||||||
const SERVICE_NAME = "NetworkService";
|
const SERVICE_NAME = "NetworkService";
|
||||||
|
|
||||||
type MonitorStatusResponseOverrides<T> = Partial<Omit<MonitorStatusResponse<T>, "monitorId" | "teamId" | "type">>;
|
|
||||||
|
|
||||||
interface BuildStatusResponseArgs<T> {
|
|
||||||
monitor: Monitor;
|
|
||||||
response?: Response<T> | null;
|
|
||||||
error?: Error | RequestError | HTTPError | null;
|
|
||||||
payload?: T | null;
|
|
||||||
jsonPath?: string;
|
|
||||||
matchMethod?: MonitorStatusResponse["matchMethod"];
|
|
||||||
expectedValue?: string;
|
|
||||||
extracted?: unknown;
|
|
||||||
overrides?: MonitorStatusResponseOverrides<T>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface INetworkService {
|
export interface INetworkService {
|
||||||
readonly serviceName: string;
|
readonly serviceName: string;
|
||||||
requestStatus(
|
requestStatus(
|
||||||
@@ -82,85 +63,27 @@ class NetworkService implements INetworkService {
|
|||||||
private PING_ERROR: number;
|
private PING_ERROR: number;
|
||||||
|
|
||||||
private axios: AxiosStatic;
|
private axios: AxiosStatic;
|
||||||
private got: Got;
|
|
||||||
private https: typeof import("https");
|
|
||||||
private jmespath: { search: (data: unknown, expression: string) => unknown };
|
|
||||||
private GameDig: { query: (options: { type: string; host: string; port?: number }) => Promise<{ ping?: number } & { [key: string]: unknown }> };
|
|
||||||
private ping: typeof import("ping");
|
|
||||||
private logger: ILogger;
|
private logger: ILogger;
|
||||||
private Docker: typeof import("dockerode");
|
private pingProvider;
|
||||||
private net: typeof import("net");
|
private httpProvider;
|
||||||
private settingsService: ISettingsService;
|
private pageSpeedProvider;
|
||||||
private grpc: typeof import("@grpc/grpc-js");
|
private hardwareProvider;
|
||||||
private protoLoader: typeof import("@grpc/proto-loader");
|
private dockerProvider;
|
||||||
|
private portProvider;
|
||||||
private buildStatusResponse = <T>({
|
private gameProvider;
|
||||||
monitor,
|
private grpcProvider;
|
||||||
response,
|
|
||||||
error,
|
|
||||||
payload,
|
|
||||||
jsonPath,
|
|
||||||
matchMethod,
|
|
||||||
expectedValue,
|
|
||||||
extracted,
|
|
||||||
overrides,
|
|
||||||
}: BuildStatusResponseArgs<T>): MonitorStatusResponse<T> => {
|
|
||||||
if (error) {
|
|
||||||
const statusResponse: MonitorStatusResponse<T> = {
|
|
||||||
monitorId: monitor.id,
|
|
||||||
teamId: monitor.teamId,
|
|
||||||
type: monitor.type,
|
|
||||||
status: false,
|
|
||||||
code: this.NETWORK_ERROR,
|
|
||||||
message: error.message ?? "Network error",
|
|
||||||
responseTime: 0,
|
|
||||||
timings: undefined,
|
|
||||||
jsonPath,
|
|
||||||
matchMethod,
|
|
||||||
expectedValue,
|
|
||||||
extracted,
|
|
||||||
payload,
|
|
||||||
};
|
|
||||||
if (error instanceof HTTPError || error instanceof RequestError) {
|
|
||||||
statusResponse.code = error?.response?.statusCode ?? this.NETWORK_ERROR;
|
|
||||||
statusResponse.message = error.message;
|
|
||||||
statusResponse.responseTime = error.timings?.phases?.total ?? 0;
|
|
||||||
statusResponse.timings = error.timings;
|
|
||||||
}
|
|
||||||
return { ...statusResponse, ...(overrides ?? {}) } as MonitorStatusResponse<T>;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
monitorId: monitor.id,
|
|
||||||
teamId: monitor.teamId,
|
|
||||||
type: monitor.type,
|
|
||||||
status: response?.ok ?? false,
|
|
||||||
code: response?.statusCode ?? this.NETWORK_ERROR,
|
|
||||||
message: response?.statusMessage ?? "",
|
|
||||||
responseTime: response?.timings?.phases?.total ?? 0,
|
|
||||||
timings: response?.timings,
|
|
||||||
payload: payload ?? response?.body,
|
|
||||||
jsonPath,
|
|
||||||
matchMethod,
|
|
||||||
expectedValue,
|
|
||||||
extracted,
|
|
||||||
...(overrides ?? {}),
|
|
||||||
} as MonitorStatusResponse<T>;
|
|
||||||
};
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
axios: AxiosStatic,
|
axios: AxiosStatic,
|
||||||
got: Got,
|
|
||||||
https: typeof import("https"),
|
|
||||||
jmespath: { search: (data: unknown, expression: string) => unknown },
|
|
||||||
GameDig: { query: (options: { type: string; host: string; port?: number }) => Promise<{ ping?: number } & { [key: string]: unknown }> },
|
|
||||||
ping: typeof import("ping"),
|
|
||||||
logger: ILogger,
|
logger: ILogger,
|
||||||
Docker: typeof import("dockerode"),
|
pingProvider: IStatusProvider<PingStatusPayload>,
|
||||||
net: typeof import("net"),
|
httpProvider: IStatusProvider<HttpStatusPayload>,
|
||||||
settingsService: ISettingsService,
|
pagespeedProvider: IStatusProvider<PageSpeedStatusPayload>,
|
||||||
grpc: typeof import("@grpc/grpc-js"),
|
hardwareProvider: IStatusProvider<HardwareStatusPayload>,
|
||||||
protoLoader: typeof import("@grpc/proto-loader")
|
dockerProvider: IStatusProvider<DockerStatusPayload>,
|
||||||
|
portProvider: IStatusProvider<PortStatusPayload>,
|
||||||
|
gameProvider: IStatusProvider<GameStatusPayload>,
|
||||||
|
grpcProvider: IStatusProvider<GrpcStatusPayload>
|
||||||
) {
|
) {
|
||||||
this.TYPE_PING = "ping";
|
this.TYPE_PING = "ping";
|
||||||
this.TYPE_HTTP = "http";
|
this.TYPE_HTTP = "http";
|
||||||
@@ -174,45 +97,22 @@ class NetworkService implements INetworkService {
|
|||||||
this.NETWORK_ERROR = 5000;
|
this.NETWORK_ERROR = 5000;
|
||||||
this.PING_ERROR = 5001;
|
this.PING_ERROR = 5001;
|
||||||
this.axios = axios;
|
this.axios = axios;
|
||||||
this.https = https;
|
|
||||||
this.jmespath = jmespath;
|
|
||||||
this.GameDig = GameDig;
|
|
||||||
this.ping = ping;
|
|
||||||
this.logger = logger;
|
this.logger = logger;
|
||||||
this.Docker = Docker;
|
|
||||||
this.net = net;
|
|
||||||
this.settingsService = settingsService;
|
|
||||||
this.grpc = grpc;
|
|
||||||
this.protoLoader = protoLoader;
|
|
||||||
|
|
||||||
const cacheable = new CacheableLookup();
|
this.pingProvider = pingProvider;
|
||||||
|
this.httpProvider = httpProvider;
|
||||||
this.got = got.extend({
|
this.pageSpeedProvider = pagespeedProvider;
|
||||||
dnsCache: cacheable,
|
this.hardwareProvider = hardwareProvider;
|
||||||
timeout: {
|
this.dockerProvider = dockerProvider;
|
||||||
request: 30000,
|
this.portProvider = portProvider;
|
||||||
},
|
this.gameProvider = gameProvider;
|
||||||
retry: { limit: 1 },
|
this.grpcProvider = grpcProvider;
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get serviceName(): string {
|
get serviceName(): string {
|
||||||
return NetworkService.SERVICE_NAME;
|
return NetworkService.SERVICE_NAME;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper functions
|
|
||||||
private async timeRequest<T>(operation: () => Promise<T>): Promise<{ response: T | null; responseTime: number; error: unknown }> {
|
|
||||||
const start = process.hrtime.bigint();
|
|
||||||
try {
|
|
||||||
const response = await operation();
|
|
||||||
const elapsedMs = Math.round(Number(process.hrtime.bigint() - start) / 1_000_000);
|
|
||||||
return { response, responseTime: elapsedMs, error: null };
|
|
||||||
} catch (error) {
|
|
||||||
const elapsedMs = Math.round(Number(process.hrtime.bigint() - start) / 1_000_000);
|
|
||||||
return { response: null, responseTime: elapsedMs, error };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Main entry point
|
// Main entry point
|
||||||
async requestStatus(
|
async requestStatus(
|
||||||
monitor: Monitor
|
monitor: Monitor
|
||||||
@@ -231,635 +131,26 @@ class NetworkService implements INetworkService {
|
|||||||
const type = monitor?.type || "unknown";
|
const type = monitor?.type || "unknown";
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case this.TYPE_PING:
|
case this.TYPE_PING:
|
||||||
return await this.requestPing(monitor);
|
return await this.pingProvider.handle(monitor);
|
||||||
case this.TYPE_HTTP:
|
case this.TYPE_HTTP:
|
||||||
return await this.requestHttp<HttpStatusPayload>(monitor);
|
return await this.httpProvider.handle(monitor);
|
||||||
case this.TYPE_PAGESPEED:
|
case this.TYPE_PAGESPEED:
|
||||||
return await this.requestPageSpeed(monitor);
|
return await this.pageSpeedProvider.handle(monitor);
|
||||||
case this.TYPE_HARDWARE:
|
case this.TYPE_HARDWARE:
|
||||||
return await this.requestHardware(monitor);
|
return await this.hardwareProvider.handle(monitor);
|
||||||
case this.TYPE_DOCKER:
|
case this.TYPE_DOCKER:
|
||||||
return await this.requestDocker(monitor);
|
return await this.dockerProvider.handle(monitor);
|
||||||
case this.TYPE_PORT:
|
case this.TYPE_PORT:
|
||||||
return await this.requestPort(monitor);
|
return await this.portProvider.handle(monitor);
|
||||||
case this.TYPE_GAME:
|
case this.TYPE_GAME:
|
||||||
return await this.requestGame(monitor);
|
return await this.gameProvider.handle(monitor);
|
||||||
case this.TYPE_GRPC:
|
case this.TYPE_GRPC:
|
||||||
return await this.requestGrpc(monitor);
|
return await this.grpcProvider.handle(monitor);
|
||||||
default:
|
default:
|
||||||
return this.handleUnsupportedType(type);
|
return this.handleUnsupportedType(type);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async requestPing(monitor: Monitor): Promise<MonitorStatusResponse<PingStatusPayload>> {
|
|
||||||
try {
|
|
||||||
if (!monitor?.url) {
|
|
||||||
throw new Error("Monitor URL is required");
|
|
||||||
}
|
|
||||||
|
|
||||||
const rawUrl = monitor.url;
|
|
||||||
const sanitizedHost = rawUrl
|
|
||||||
.replace(/^https?:\/\//, "")
|
|
||||||
.replace(/\/.*$/, "")
|
|
||||||
.replace(/:.*/, "");
|
|
||||||
const { response, error } = await this.timeRequest(() => this.ping.promise.probe(sanitizedHost));
|
|
||||||
|
|
||||||
if (!response) {
|
|
||||||
if (error) {
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
throw new Error("Ping failed - no result returned");
|
|
||||||
}
|
|
||||||
|
|
||||||
const pingResponse = this.buildStatusResponse<PingStatusPayload>({
|
|
||||||
monitor,
|
|
||||||
payload: response as PingStatusPayload,
|
|
||||||
overrides: {
|
|
||||||
status: (response as { alive?: boolean })?.alive ?? false,
|
|
||||||
code: 200,
|
|
||||||
message: "Success",
|
|
||||||
responseTime: (response as { time?: number })?.time ?? 0,
|
|
||||||
payload: response as PingStatusPayload,
|
|
||||||
} as MonitorStatusResponseOverrides<PingStatusPayload>,
|
|
||||||
});
|
|
||||||
|
|
||||||
return pingResponse;
|
|
||||||
} catch (err: unknown) {
|
|
||||||
const originalMessage = err instanceof Error ? err.message : String(err);
|
|
||||||
throw new AppError({
|
|
||||||
message: originalMessage || "Error performing ping",
|
|
||||||
service: this.SERVICE_NAME,
|
|
||||||
method: "requestPing",
|
|
||||||
details: { url: monitor.url },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async requestHttp<T = unknown>(monitor: Monitor): Promise<MonitorStatusResponse<T>> {
|
|
||||||
const { url, secret, ignoreTlsErrors, useAdvancedMatching, jsonPath, matchMethod, expectedValue, basicAuthUser, basicAuthPassword } = monitor;
|
|
||||||
const httpResponse = this.buildStatusResponse<T>({
|
|
||||||
monitor,
|
|
||||||
overrides: {
|
|
||||||
status: false,
|
|
||||||
code: this.NETWORK_ERROR,
|
|
||||||
message: "Request not executed",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (!url) {
|
|
||||||
throw new Error("Monitor URL is required");
|
|
||||||
}
|
|
||||||
const headers: Record<string, string> = {};
|
|
||||||
if (secret) {
|
|
||||||
headers["Authorization"] = `Bearer ${secret}`;
|
|
||||||
} else if (basicAuthUser) {
|
|
||||||
const credentials = Buffer.from(`${basicAuthUser}:${basicAuthPassword || ""}`).toString("base64");
|
|
||||||
headers["Authorization"] = `Basic ${credentials}`;
|
|
||||||
}
|
|
||||||
const config: Record<string, unknown> = {
|
|
||||||
headers: Object.keys(headers).length > 0 ? headers : undefined,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (ignoreTlsErrors) {
|
|
||||||
config.agent = {
|
|
||||||
https: new this.https.Agent({
|
|
||||||
rejectUnauthorized: false,
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const response = await this.got(url, config);
|
|
||||||
|
|
||||||
let payload: T | string | undefined;
|
|
||||||
const contentType = response.headers["content-type"];
|
|
||||||
|
|
||||||
if (contentType && contentType.includes("application/json")) {
|
|
||||||
try {
|
|
||||||
payload = JSON.parse(response.body);
|
|
||||||
} catch {
|
|
||||||
payload = response.body;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
payload = response.body;
|
|
||||||
}
|
|
||||||
|
|
||||||
Object.assign(httpResponse, {
|
|
||||||
code: response.statusCode,
|
|
||||||
status: response.ok,
|
|
||||||
message: response.statusMessage ?? "",
|
|
||||||
responseTime: response.timings.phases.total || 0,
|
|
||||||
payload,
|
|
||||||
timings: response.timings,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!useAdvancedMatching) {
|
|
||||||
return httpResponse;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (expectedValue && !jsonPath) {
|
|
||||||
let ok = false;
|
|
||||||
if (matchMethod === "equal") ok = payload === expectedValue;
|
|
||||||
if (matchMethod === "include" && typeof payload === "string") ok = payload.includes(expectedValue);
|
|
||||||
if (matchMethod === "regex" && typeof payload === "string") ok = new RegExp(expectedValue).test(payload);
|
|
||||||
|
|
||||||
if (ok === true) {
|
|
||||||
return httpResponse;
|
|
||||||
} else {
|
|
||||||
httpResponse.code = 500;
|
|
||||||
httpResponse.status = false;
|
|
||||||
httpResponse.message = "Expected value did not match";
|
|
||||||
return httpResponse;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (jsonPath) {
|
|
||||||
const contentType = response.headers["content-type"];
|
|
||||||
const isJson = contentType?.includes("application/json");
|
|
||||||
if (!isJson) {
|
|
||||||
httpResponse.status = false;
|
|
||||||
httpResponse.message = "Response is not JSON";
|
|
||||||
return httpResponse;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const extracted = this.jmespath.search(payload, jsonPath);
|
|
||||||
if (expectedValue) {
|
|
||||||
let ok = false;
|
|
||||||
if (matchMethod === "equal") ok = extracted === expectedValue;
|
|
||||||
if (matchMethod === "include" && typeof extracted === "string") ok = extracted.includes(expectedValue);
|
|
||||||
if (matchMethod === "regex" && typeof extracted === "string") ok = new RegExp(expectedValue).test(extracted);
|
|
||||||
|
|
||||||
if (ok) {
|
|
||||||
httpResponse.extracted = extracted;
|
|
||||||
return httpResponse;
|
|
||||||
} else {
|
|
||||||
httpResponse.status = false;
|
|
||||||
httpResponse.code = 500;
|
|
||||||
httpResponse.message = "Expected value did not match";
|
|
||||||
httpResponse.extracted = extracted;
|
|
||||||
return httpResponse;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const isFalsey = extracted === false || extracted === "false" || extracted === undefined || extracted === null;
|
|
||||||
if (!isFalsey) {
|
|
||||||
httpResponse.extracted = extracted;
|
|
||||||
return httpResponse;
|
|
||||||
} else {
|
|
||||||
httpResponse.status = false;
|
|
||||||
httpResponse.code = 500;
|
|
||||||
httpResponse.message = "Expected value did not match";
|
|
||||||
httpResponse.extracted = extracted;
|
|
||||||
return httpResponse;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
httpResponse.status = false;
|
|
||||||
httpResponse.message = "Error evaluating JSON path";
|
|
||||||
return httpResponse;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return httpResponse;
|
|
||||||
} catch (err: unknown) {
|
|
||||||
if (err instanceof HTTPError || err instanceof RequestError) {
|
|
||||||
httpResponse.code = err.response?.statusCode || this.NETWORK_ERROR;
|
|
||||||
httpResponse.status = false;
|
|
||||||
httpResponse.message = String(err.response?.statusCode) || err.message;
|
|
||||||
httpResponse.responseTime = err.timings?.phases?.total || 0;
|
|
||||||
httpResponse.payload = null;
|
|
||||||
httpResponse.timings = err.timings || undefined;
|
|
||||||
return httpResponse;
|
|
||||||
}
|
|
||||||
const originalMessage = err instanceof Error ? err.message : String(err);
|
|
||||||
throw new AppError({
|
|
||||||
message: originalMessage || "Error performing HTTP request",
|
|
||||||
service: this.SERVICE_NAME,
|
|
||||||
method: "requestHttp",
|
|
||||||
details: { url: monitor.url },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async requestPageSpeed(monitor: Monitor): Promise<MonitorStatusResponse<PageSpeedStatusPayload>> {
|
|
||||||
try {
|
|
||||||
const url = monitor.url;
|
|
||||||
if (!url) {
|
|
||||||
throw new Error("Monitor URL is required");
|
|
||||||
}
|
|
||||||
let pageSpeedUrl = `https://pagespeedonline.googleapis.com/pagespeedonline/v5/runPagespeed?url=${url}&category=seo&category=accessibility&category=best-practices&category=performance`;
|
|
||||||
const dbSettings = await this.settingsService.getDBSettings();
|
|
||||||
if (dbSettings?.pagespeedApiKey) {
|
|
||||||
pageSpeedUrl += `&key=${dbSettings.pagespeedApiKey}`;
|
|
||||||
} else {
|
|
||||||
this.logger.warn({
|
|
||||||
message: "PageSpeed API key not found, job not executed",
|
|
||||||
service: this.SERVICE_NAME,
|
|
||||||
method: "requestPagespeed",
|
|
||||||
details: { url },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return await this.requestHttp<PageSpeedStatusPayload>({
|
|
||||||
...monitor,
|
|
||||||
url: pageSpeedUrl,
|
|
||||||
});
|
|
||||||
} catch (err: unknown) {
|
|
||||||
const originalMessage = err instanceof Error ? err.message : String(err);
|
|
||||||
throw new AppError({
|
|
||||||
message: originalMessage || "Error performing PageSpeed request",
|
|
||||||
service: this.SERVICE_NAME,
|
|
||||||
method: "requestPageSpeed",
|
|
||||||
details: { url: monitor.url },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async requestHardware(monitor: Monitor): Promise<MonitorStatusResponse<HardwareStatusPayload>> {
|
|
||||||
try {
|
|
||||||
return await this.requestHttp<HardwareStatusPayload>(monitor);
|
|
||||||
} catch (err: unknown) {
|
|
||||||
const originalMessage = err instanceof Error ? err.message : String(err);
|
|
||||||
throw new AppError({
|
|
||||||
message: originalMessage || "Error performing hardware request",
|
|
||||||
service: this.SERVICE_NAME,
|
|
||||||
method: "requestHardware",
|
|
||||||
details: { url: monitor.url },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async requestDocker(monitor: Monitor): Promise<MonitorStatusResponse<DockerStatusPayload>> {
|
|
||||||
try {
|
|
||||||
if (!monitor.url) {
|
|
||||||
throw new Error("Monitor URL is required");
|
|
||||||
}
|
|
||||||
|
|
||||||
const docker = new this.Docker({
|
|
||||||
socketPath: "/var/run/docker.sock",
|
|
||||||
});
|
|
||||||
|
|
||||||
const dockerResponse = this.buildStatusResponse<DockerStatusPayload>({
|
|
||||||
monitor,
|
|
||||||
overrides: {
|
|
||||||
status: false,
|
|
||||||
code: this.NETWORK_ERROR,
|
|
||||||
message: "No response",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const containers = await docker.listContainers({ all: true });
|
|
||||||
|
|
||||||
// Normalize input: strip leading slashes and convert to lowercase for comparison
|
|
||||||
const normalizedInput = monitor.url.replace(/^\/+/, "").toLowerCase();
|
|
||||||
|
|
||||||
// Priority-based matching to avoid ambiguity:
|
|
||||||
// 1. Exact full ID match (64-char)
|
|
||||||
const exactIdMatch = containers.find((c) => c.Id.toLowerCase() === normalizedInput);
|
|
||||||
|
|
||||||
// 2. Exact container name match (case-insensitive)
|
|
||||||
const exactNameMatch = containers.find((c) =>
|
|
||||||
c.Names.some((name: string) => {
|
|
||||||
const cleanName = name.replace(/^\/+/, "").toLowerCase();
|
|
||||||
return cleanName === normalizedInput;
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
// 3. Partial ID match (fallback for backwards compatibility)
|
|
||||||
const partialIdMatch = containers.find((c) => c.Id.toLowerCase().startsWith(normalizedInput));
|
|
||||||
|
|
||||||
// Select container based on priority
|
|
||||||
const targetContainer = exactIdMatch || exactNameMatch || partialIdMatch;
|
|
||||||
|
|
||||||
// Return negative response if no container
|
|
||||||
if (!targetContainer) {
|
|
||||||
this.logger.warn({
|
|
||||||
message: `No container found for "${monitor.url}".`,
|
|
||||||
service: this.SERVICE_NAME,
|
|
||||||
method: "requestDocker",
|
|
||||||
details: { url: monitor.url },
|
|
||||||
});
|
|
||||||
|
|
||||||
dockerResponse.code = 404;
|
|
||||||
dockerResponse.status = false;
|
|
||||||
dockerResponse.message = "Docker container not found";
|
|
||||||
return dockerResponse;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return negative response if ambiguous matches exist
|
|
||||||
const matchTypes = [];
|
|
||||||
if (exactIdMatch) matchTypes.push("exact ID");
|
|
||||||
if (exactNameMatch) matchTypes.push("exact name");
|
|
||||||
if (partialIdMatch && !exactIdMatch) matchTypes.push("partial ID");
|
|
||||||
|
|
||||||
if (matchTypes.length > 1) {
|
|
||||||
this.logger.warn({
|
|
||||||
message: `Ambiguous container match for "${monitor.url}". Matched by: ${matchTypes.join(", ")}. Using ${exactIdMatch ? "exact ID" : exactNameMatch ? "exact name" : "partial ID"} match.`,
|
|
||||||
service: this.SERVICE_NAME,
|
|
||||||
method: "requestDocker",
|
|
||||||
details: { url: monitor.url },
|
|
||||||
});
|
|
||||||
dockerResponse.status = false;
|
|
||||||
dockerResponse.message = `Ambiguous container match for "${monitor.url}". Matched by: ${matchTypes.join(", ")}. Using ${exactIdMatch ? "exact ID" : exactNameMatch ? "exact name" : "partial ID"} match.`;
|
|
||||||
return dockerResponse;
|
|
||||||
}
|
|
||||||
|
|
||||||
const container = docker.getContainer(targetContainer.Id);
|
|
||||||
const { response, responseTime, error }: { response?: { State: { Status: string } } | null; responseTime: number; error: unknown } =
|
|
||||||
await this.timeRequest(() => container.inspect());
|
|
||||||
|
|
||||||
dockerResponse.responseTime = responseTime;
|
|
||||||
dockerResponse.status = response?.State?.Status === "running" ? true : false;
|
|
||||||
dockerResponse.code = 200;
|
|
||||||
dockerResponse.message = "Docker container status fetched successfully";
|
|
||||||
|
|
||||||
if (error) {
|
|
||||||
dockerResponse.status = false;
|
|
||||||
dockerResponse.code =
|
|
||||||
(error && typeof error === "object" && "statusCode" in error ? (error as { statusCode?: number }).statusCode : undefined) ||
|
|
||||||
this.NETWORK_ERROR;
|
|
||||||
dockerResponse.message =
|
|
||||||
(error && typeof error === "object" && "reason" in error ? (error as { reason?: string }).reason : undefined) ||
|
|
||||||
"Failed to fetch Docker container information";
|
|
||||||
return dockerResponse;
|
|
||||||
}
|
|
||||||
|
|
||||||
return dockerResponse;
|
|
||||||
} catch (err: unknown) {
|
|
||||||
const originalMessage = err instanceof Error ? err.message : String(err);
|
|
||||||
throw new AppError({
|
|
||||||
message: originalMessage || "Error performing Docker request",
|
|
||||||
service: this.SERVICE_NAME,
|
|
||||||
method: "requestDocker",
|
|
||||||
details: { url: monitor.url },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async requestPort(monitor: Monitor): Promise<MonitorStatusResponse<PortStatusPayload>> {
|
|
||||||
try {
|
|
||||||
const { url, port } = monitor;
|
|
||||||
if (!port) {
|
|
||||||
throw new Error("Port is required for port monitor");
|
|
||||||
}
|
|
||||||
|
|
||||||
const { response, responseTime, error } = await this.timeRequest(async () => {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const socket = this.net.createConnection(
|
|
||||||
{
|
|
||||||
host: url,
|
|
||||||
port,
|
|
||||||
},
|
|
||||||
() => {
|
|
||||||
socket.end();
|
|
||||||
socket.destroy();
|
|
||||||
resolve({ success: true });
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
socket.setTimeout(5000);
|
|
||||||
socket.on("timeout", () => {
|
|
||||||
socket.destroy();
|
|
||||||
reject(new Error("Connection timeout"));
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.on("error", (err: unknown) => {
|
|
||||||
socket.destroy();
|
|
||||||
reject(err);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
const portResponse = this.buildStatusResponse<PortStatusPayload>({
|
|
||||||
monitor,
|
|
||||||
overrides: {
|
|
||||||
code: 200,
|
|
||||||
status: (response as { success?: boolean })?.success ?? false,
|
|
||||||
message: "Port check successful",
|
|
||||||
responseTime,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (error) {
|
|
||||||
portResponse.code = this.NETWORK_ERROR;
|
|
||||||
portResponse.status = false;
|
|
||||||
portResponse.message = "Port check failed";
|
|
||||||
return portResponse;
|
|
||||||
}
|
|
||||||
|
|
||||||
return portResponse;
|
|
||||||
} catch (err: unknown) {
|
|
||||||
const originalMessage = err instanceof Error ? err.message : String(err);
|
|
||||||
throw new AppError({
|
|
||||||
message: originalMessage || "Error performing port check",
|
|
||||||
service: this.SERVICE_NAME,
|
|
||||||
method: "requestPort",
|
|
||||||
details: { url: monitor.url, port: monitor.port },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async requestGame(monitor: Monitor): Promise<MonitorStatusResponse<GameStatusPayload>> {
|
|
||||||
try {
|
|
||||||
const { url, port, gameId } = monitor;
|
|
||||||
|
|
||||||
const gameResponse = this.buildStatusResponse<GameStatusPayload>({
|
|
||||||
monitor,
|
|
||||||
overrides: {
|
|
||||||
code: 200,
|
|
||||||
status: true,
|
|
||||||
message: "Success",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const state = await this.GameDig.query({
|
|
||||||
type: gameId ?? "unknown",
|
|
||||||
host: url,
|
|
||||||
port: port ?? 0,
|
|
||||||
}).catch((error: unknown): undefined => {
|
|
||||||
this.logger.warn({
|
|
||||||
message: error instanceof Error ? error.message : String(error),
|
|
||||||
service: this.SERVICE_NAME,
|
|
||||||
method: "requestGame",
|
|
||||||
details: { url, port, gameId },
|
|
||||||
});
|
|
||||||
return undefined;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!state) {
|
|
||||||
gameResponse.code = this.NETWORK_ERROR;
|
|
||||||
gameResponse.status = false;
|
|
||||||
gameResponse.message = "No response";
|
|
||||||
return gameResponse;
|
|
||||||
}
|
|
||||||
|
|
||||||
gameResponse.responseTime = state.ping ?? 0;
|
|
||||||
gameResponse.payload = state as GameStatusPayload;
|
|
||||||
return gameResponse;
|
|
||||||
} catch (err: unknown) {
|
|
||||||
const originalMessage = err instanceof Error ? err.message : String(err);
|
|
||||||
throw new AppError({
|
|
||||||
message: originalMessage || "Error performing game server check",
|
|
||||||
service: this.SERVICE_NAME,
|
|
||||||
method: "requestGame",
|
|
||||||
details: { url: monitor.url, port: monitor.port, gameId: monitor.gameId },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
private async requestGrpc(monitor: Monitor): Promise<MonitorStatusResponse<GrpcStatusPayload>> {
|
|
||||||
try {
|
|
||||||
const { url, port, ignoreTlsErrors } = monitor;
|
|
||||||
const grpcServiceName = monitor.grpcServiceName || "";
|
|
||||||
|
|
||||||
if (!url) {
|
|
||||||
throw new AppError({ message: "Monitor host is required", service: this.SERVICE_NAME, method: "requestGrpc" });
|
|
||||||
}
|
|
||||||
if (!port) {
|
|
||||||
throw new AppError({ message: "Monitor port is required", service: this.SERVICE_NAME, method: "requestGrpc" });
|
|
||||||
}
|
|
||||||
|
|
||||||
const target = `${url}:${port}`;
|
|
||||||
|
|
||||||
const currentFilePath = fileURLToPath(import.meta.url);
|
|
||||||
const protoPath = path.join(path.dirname(currentFilePath), "protos", "health.proto");
|
|
||||||
|
|
||||||
const packageDefinition = this.protoLoader.loadSync(protoPath, {
|
|
||||||
keepCase: true,
|
|
||||||
longs: String,
|
|
||||||
enums: String,
|
|
||||||
defaults: true,
|
|
||||||
oneofs: true,
|
|
||||||
});
|
|
||||||
const grpcObject = this.grpc.loadPackageDefinition(packageDefinition) as unknown as {
|
|
||||||
grpc: {
|
|
||||||
health: {
|
|
||||||
v1: {
|
|
||||||
Health: new (
|
|
||||||
target: string,
|
|
||||||
credentials: unknown
|
|
||||||
) => {
|
|
||||||
Check: (request: { service: string }, options: { deadline: Date }, callback: (err: unknown, response: unknown) => void) => void;
|
|
||||||
close: () => void;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
const healthService = grpcObject.grpc.health.v1.Health;
|
|
||||||
|
|
||||||
let credentials;
|
|
||||||
if (ignoreTlsErrors) {
|
|
||||||
credentials = this.grpc.credentials.createSsl(null, null, null, {
|
|
||||||
checkServerIdentity: () => undefined,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
credentials = this.grpc.credentials.createInsecure();
|
|
||||||
}
|
|
||||||
|
|
||||||
const client = new healthService(target, credentials);
|
|
||||||
|
|
||||||
const TIMEOUT_MS = 10000;
|
|
||||||
const deadline = new Date(Date.now() + TIMEOUT_MS);
|
|
||||||
|
|
||||||
const grpcResponse = this.buildStatusResponse<GrpcStatusPayload>({
|
|
||||||
monitor,
|
|
||||||
overrides: {
|
|
||||||
status: false,
|
|
||||||
code: this.NETWORK_ERROR,
|
|
||||||
message: "gRPC health check not executed",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const { response, responseTime, error } = await this.timeRequest<GrpcStatusPayload>(() => {
|
|
||||||
return new Promise<GrpcStatusPayload>((resolve, reject) => {
|
|
||||||
client.Check({ service: grpcServiceName }, { deadline }, (err: unknown, response: unknown) => {
|
|
||||||
client.close();
|
|
||||||
|
|
||||||
if (err) {
|
|
||||||
const grpcErr = err as { code?: number; details?: string; message?: string };
|
|
||||||
const payload: GrpcStatusPayload = {
|
|
||||||
grpcStatusCode: grpcErr.code ?? -1,
|
|
||||||
grpcStatusName: this.getGrpcStatusName(grpcErr.code ?? -1),
|
|
||||||
serviceName: grpcServiceName,
|
|
||||||
servingStatus: "UNKNOWN",
|
|
||||||
};
|
|
||||||
const grpcError = new AppError({
|
|
||||||
message: grpcErr.details || grpcErr.message || "gRPC error",
|
|
||||||
service: this.SERVICE_NAME,
|
|
||||||
method: "requestGrpc",
|
|
||||||
}) as AppError & { grpcPayload?: GrpcStatusPayload; grpcCode?: number };
|
|
||||||
grpcError.grpcPayload = payload;
|
|
||||||
grpcError.grpcCode = grpcErr.code;
|
|
||||||
reject(grpcError);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const resp = response as { status?: string } | undefined;
|
|
||||||
const servingStatus = resp?.status ?? "UNKNOWN";
|
|
||||||
resolve({
|
|
||||||
grpcStatusCode: 0,
|
|
||||||
grpcStatusName: "OK",
|
|
||||||
serviceName: grpcServiceName,
|
|
||||||
servingStatus,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
if (error) {
|
|
||||||
const grpcError = error as AppError & { grpcPayload?: GrpcStatusPayload; grpcCode?: number };
|
|
||||||
const payload = grpcError.grpcPayload;
|
|
||||||
grpcResponse.status = false;
|
|
||||||
grpcResponse.code = grpcError.grpcCode ?? this.NETWORK_ERROR;
|
|
||||||
grpcResponse.message = grpcError.message ?? "gRPC health check failed";
|
|
||||||
grpcResponse.responseTime = responseTime;
|
|
||||||
grpcResponse.payload = payload ?? null;
|
|
||||||
return grpcResponse;
|
|
||||||
}
|
|
||||||
|
|
||||||
const grpcPayload = response as GrpcStatusPayload;
|
|
||||||
const isServing = grpcPayload.servingStatus === "SERVING";
|
|
||||||
|
|
||||||
grpcResponse.status = isServing;
|
|
||||||
grpcResponse.code = isServing ? 200 : this.NETWORK_ERROR;
|
|
||||||
grpcResponse.message = isServing
|
|
||||||
? `gRPC service healthy (${grpcPayload.servingStatus})`
|
|
||||||
: `gRPC service unhealthy (${grpcPayload.servingStatus})`;
|
|
||||||
grpcResponse.responseTime = responseTime;
|
|
||||||
grpcResponse.payload = grpcPayload;
|
|
||||||
|
|
||||||
return grpcResponse;
|
|
||||||
} catch (err: unknown) {
|
|
||||||
const originalMessage = err instanceof Error ? err.message : String(err);
|
|
||||||
throw new AppError({
|
|
||||||
message: originalMessage || "Error performing gRPC health check",
|
|
||||||
service: this.SERVICE_NAME,
|
|
||||||
method: "requestGrpc",
|
|
||||||
details: { url: monitor.url, port: monitor.port, grpcServiceName: monitor.grpcServiceName },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private getGrpcStatusName(code: number): string {
|
|
||||||
const statusNames: Record<number, string> = {
|
|
||||||
0: "OK",
|
|
||||||
1: "CANCELLED",
|
|
||||||
2: "UNKNOWN",
|
|
||||||
3: "INVALID_ARGUMENT",
|
|
||||||
4: "DEADLINE_EXCEEDED",
|
|
||||||
5: "NOT_FOUND",
|
|
||||||
6: "ALREADY_EXISTS",
|
|
||||||
7: "PERMISSION_DENIED",
|
|
||||||
8: "RESOURCE_EXHAUSTED",
|
|
||||||
9: "FAILED_PRECONDITION",
|
|
||||||
10: "ABORTED",
|
|
||||||
11: "OUT_OF_RANGE",
|
|
||||||
12: "UNIMPLEMENTED",
|
|
||||||
13: "INTERNAL",
|
|
||||||
14: "UNAVAILABLE",
|
|
||||||
15: "DATA_LOSS",
|
|
||||||
16: "UNAUTHENTICATED",
|
|
||||||
};
|
|
||||||
return statusNames[code] || "UNKNOWN";
|
|
||||||
}
|
|
||||||
|
|
||||||
private async handleUnsupportedType(type: string): Promise<MonitorStatusResponse> {
|
private async handleUnsupportedType(type: string): Promise<MonitorStatusResponse> {
|
||||||
return {
|
return {
|
||||||
monitorId: "unknown",
|
monitorId: "unknown",
|
||||||
|
|||||||
@@ -27,8 +27,6 @@ export interface Monitor {
|
|||||||
jsonPath?: string;
|
jsonPath?: string;
|
||||||
expectedValue?: string;
|
expectedValue?: string;
|
||||||
matchMethod?: MonitorMatchMethod;
|
matchMethod?: MonitorMatchMethod;
|
||||||
basicAuthUser?: string;
|
|
||||||
basicAuthPassword?: string;
|
|
||||||
url: string;
|
url: string;
|
||||||
port?: number;
|
port?: number;
|
||||||
isActive: boolean;
|
isActive: boolean;
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ import type {
|
|||||||
MonitorStatus,
|
MonitorStatus,
|
||||||
} from "@/types/index.js";
|
} from "@/types/index.js";
|
||||||
|
|
||||||
|
import type { QueryResult } from "gamedig";
|
||||||
|
|
||||||
export interface MonitorStatusResponse<
|
export interface MonitorStatusResponse<
|
||||||
T =
|
T =
|
||||||
| HttpStatusPayload
|
| HttpStatusPayload
|
||||||
@@ -49,7 +51,7 @@ export interface PingStatusPayload {
|
|||||||
host: string;
|
host: string;
|
||||||
numeric_host?: string;
|
numeric_host?: string;
|
||||||
alive: boolean;
|
alive: boolean;
|
||||||
time: number;
|
time: number | unknown;
|
||||||
times?: number[];
|
times?: number[];
|
||||||
output?: string;
|
output?: string;
|
||||||
min?: string;
|
min?: string;
|
||||||
@@ -100,7 +102,7 @@ export interface PortStatusPayload {
|
|||||||
success: boolean;
|
success: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type GameStatusPayload = Record<string, unknown>;
|
export type GameStatusPayload = QueryResult;
|
||||||
|
|
||||||
export interface GrpcStatusPayload {
|
export interface GrpcStatusPayload {
|
||||||
grpcStatusCode: number;
|
grpcStatusCode: number;
|
||||||
@@ -134,3 +136,5 @@ export type StatusChangeResult = {
|
|||||||
temp: boolean;
|
temp: boolean;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type MonitorStatusResponseOverrides<T> = Partial<Omit<MonitorStatusResponse<T>, "monitorId" | "teamId" | "type">>;
|
||||||
@@ -70,8 +70,6 @@ export const createMonitorBodyValidation = z.object({
|
|||||||
jsonPath: z.union([z.string(), z.literal("")]).optional(),
|
jsonPath: z.union([z.string(), z.literal("")]).optional(),
|
||||||
expectedValue: z.union([z.string(), z.literal("")]).optional(),
|
expectedValue: z.union([z.string(), z.literal("")]).optional(),
|
||||||
matchMethod: z.union([z.string(), z.null(), z.literal("")]).optional(),
|
matchMethod: z.union([z.string(), z.null(), z.literal("")]).optional(),
|
||||||
basicAuthUser: z.union([z.string(), z.literal("")]).optional(),
|
|
||||||
basicAuthPassword: z.union([z.string(), z.literal("")]).optional(),
|
|
||||||
gameId: z.union([z.string(), z.literal("")]).optional(),
|
gameId: z.union([z.string(), z.literal("")]).optional(),
|
||||||
grpcServiceName: z.union([z.string(), z.literal("")]).default(""),
|
grpcServiceName: z.union([z.string(), z.literal("")]).default(""),
|
||||||
selectedDisks: z.array(z.string()).optional(),
|
selectedDisks: z.array(z.string()).optional(),
|
||||||
@@ -96,8 +94,6 @@ export const editMonitorBodyValidation = z.object({
|
|||||||
jsonPath: z.union([z.string(), z.literal("")]).optional(),
|
jsonPath: z.union([z.string(), z.literal("")]).optional(),
|
||||||
expectedValue: z.union([z.string(), z.literal("")]).optional(),
|
expectedValue: z.union([z.string(), z.literal("")]).optional(),
|
||||||
matchMethod: z.union([z.string(), z.null(), z.literal("")]).optional(),
|
matchMethod: z.union([z.string(), z.null(), z.literal("")]).optional(),
|
||||||
basicAuthUser: z.union([z.string(), z.literal("")]).optional(),
|
|
||||||
basicAuthPassword: z.union([z.string(), z.literal("")]).optional(),
|
|
||||||
port: z.number().min(1).max(65535).optional(),
|
port: z.number().min(1).max(65535).optional(),
|
||||||
cpuAlertThreshold: z.number().optional(),
|
cpuAlertThreshold: z.number().optional(),
|
||||||
memoryAlertThreshold: z.number().optional(),
|
memoryAlertThreshold: z.number().optional(),
|
||||||
|
|||||||
Reference in new issue
Block a user