Compare commits
9
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
34d55800e7 | ||
|
|
298018035d | ||
|
|
eaf9522706 | ||
|
|
2bc797d349 | ||
|
|
d23048ae54 | ||
|
|
a7f8aa8eb9 | ||
|
|
df77ed1fc7 | ||
|
|
52b838a9eb | ||
|
|
5093ca9f63 |
No files matched your search
Generated
+25
-67
@@ -83,6 +83,7 @@
|
||||
"node_modules/@babel/core": {
|
||||
"version": "7.29.0",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.29.0",
|
||||
"@babel/generator": "^7.29.0",
|
||||
@@ -353,6 +354,7 @@
|
||||
"node_modules/@emotion/react": {
|
||||
"version": "11.14.0",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.18.3",
|
||||
"@emotion/babel-plugin": "^11.13.5",
|
||||
@@ -390,6 +392,7 @@
|
||||
"node_modules/@emotion/styled": {
|
||||
"version": "11.14.1",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.18.3",
|
||||
"@emotion/babel-plugin": "^11.13.5",
|
||||
@@ -434,7 +437,6 @@
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -451,7 +453,6 @@
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -468,7 +469,6 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -485,7 +485,6 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -502,7 +501,6 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -519,7 +517,6 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -536,7 +533,6 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -553,7 +549,6 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -570,7 +565,6 @@
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -587,7 +581,6 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -604,7 +597,6 @@
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -621,7 +613,6 @@
|
||||
"cpu": [
|
||||
"loong64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -638,7 +629,6 @@
|
||||
"cpu": [
|
||||
"mips64el"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -655,7 +645,6 @@
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -672,7 +661,6 @@
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -689,7 +677,6 @@
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -704,7 +691,6 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -721,7 +707,6 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -738,7 +723,6 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -755,7 +739,6 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -772,7 +755,6 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -789,7 +771,6 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -806,7 +787,6 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -823,7 +803,6 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -840,7 +819,6 @@
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -857,7 +835,6 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1185,6 +1162,7 @@
|
||||
"node_modules/@mui/material": {
|
||||
"version": "7.3.7",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.28.4",
|
||||
"@mui/core-downloads-tracker": "^7.3.7",
|
||||
@@ -1289,6 +1267,7 @@
|
||||
"node_modules/@mui/system": {
|
||||
"version": "7.3.7",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.28.4",
|
||||
"@mui/private-theming": "^7.3.7",
|
||||
@@ -1666,7 +1645,6 @@
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1680,7 +1658,6 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1694,7 +1671,6 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1708,7 +1684,6 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1722,7 +1697,6 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1736,7 +1710,6 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1750,7 +1723,6 @@
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1764,7 +1736,6 @@
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1778,7 +1749,6 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1792,7 +1762,6 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1806,7 +1775,6 @@
|
||||
"cpu": [
|
||||
"loong64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1820,7 +1788,6 @@
|
||||
"cpu": [
|
||||
"loong64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1834,7 +1801,6 @@
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1848,7 +1814,6 @@
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1862,7 +1827,6 @@
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1876,7 +1840,6 @@
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1890,7 +1853,6 @@
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1902,7 +1864,6 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1916,7 +1877,6 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1930,7 +1890,6 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1944,7 +1903,6 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1958,7 +1916,6 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1972,7 +1929,6 @@
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1986,7 +1942,6 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -2000,7 +1955,6 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -2350,8 +2304,9 @@
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "24.5.2",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"undici-types": "~7.12.0"
|
||||
}
|
||||
@@ -2366,8 +2321,8 @@
|
||||
},
|
||||
"node_modules/@types/react": {
|
||||
"version": "18.3.27",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/prop-types": "*",
|
||||
"csstype": "^3.2.2"
|
||||
@@ -2489,6 +2444,7 @@
|
||||
"version": "8.15.0",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"acorn": "bin/acorn"
|
||||
},
|
||||
@@ -2811,6 +2767,7 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"baseline-browser-mapping": "^2.9.0",
|
||||
"caniuse-lite": "^1.0.30001759",
|
||||
@@ -3242,7 +3199,8 @@
|
||||
},
|
||||
"node_modules/dayjs": {
|
||||
"version": "1.11.13",
|
||||
"license": "MIT"
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/debug": {
|
||||
"version": "4.4.3",
|
||||
@@ -3566,7 +3524,6 @@
|
||||
},
|
||||
"node_modules/esbuild": {
|
||||
"version": "0.25.12",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
@@ -3625,6 +3582,7 @@
|
||||
"version": "8.57.1",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.2.0",
|
||||
"@eslint-community/regexpp": "^4.6.1",
|
||||
@@ -3884,7 +3842,6 @@
|
||||
},
|
||||
"node_modules/fdir": {
|
||||
"version": "6.5.0",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
@@ -4018,7 +3975,6 @@
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
||||
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
@@ -4371,6 +4327,7 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.27.6"
|
||||
},
|
||||
@@ -5154,6 +5111,7 @@
|
||||
"resolved": "https://registry.npmjs.org/maplibre-gl/-/maplibre-gl-5.19.0.tgz",
|
||||
"integrity": "sha512-REhYUN8gNP3HlcIZS6QU2uy8iovl31cXsrNDkCcqWSQbCkcpdYLczqDz5PVIwNH42UQNyvukjes/RoHPDrOUmQ==",
|
||||
"license": "BSD-3-Clause",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@mapbox/geojson-rewind": "^0.5.2",
|
||||
"@mapbox/jsonlint-lines-primitives": "^2.0.2",
|
||||
@@ -5317,7 +5275,6 @@
|
||||
},
|
||||
"node_modules/nanoid": {
|
||||
"version": "3.3.11",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
@@ -5650,7 +5607,6 @@
|
||||
},
|
||||
"node_modules/postcss": {
|
||||
"version": "8.5.6",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
@@ -5801,6 +5757,7 @@
|
||||
"node_modules/react-dom": {
|
||||
"version": "18.3.1",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.1.0",
|
||||
"scheduler": "^0.23.2"
|
||||
@@ -5812,6 +5769,7 @@
|
||||
"node_modules/react-hook-form": {
|
||||
"version": "7.71.1",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
@@ -6011,7 +5969,8 @@
|
||||
},
|
||||
"node_modules/redux": {
|
||||
"version": "5.0.1",
|
||||
"license": "MIT"
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/redux-persist": {
|
||||
"version": "6.0.0",
|
||||
@@ -6158,8 +6117,8 @@
|
||||
},
|
||||
"node_modules/rollup": {
|
||||
"version": "4.57.1",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/estree": "1.0.8"
|
||||
},
|
||||
@@ -6530,7 +6489,6 @@
|
||||
},
|
||||
"node_modules/source-map-js": {
|
||||
"version": "1.2.1",
|
||||
"dev": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
@@ -6818,7 +6776,6 @@
|
||||
},
|
||||
"node_modules/tinyglobby": {
|
||||
"version": "0.2.15",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fdir": "^6.5.0",
|
||||
@@ -6948,8 +6905,9 @@
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "5.9.3",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
@@ -6992,7 +6950,7 @@
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "7.12.0",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/union-value": {
|
||||
@@ -7082,8 +7040,8 @@
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "6.3.6",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"esbuild": "^0.25.0",
|
||||
"fdir": "^6.4.4",
|
||||
@@ -7366,7 +7324,7 @@
|
||||
"version": "2.8.2",
|
||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz",
|
||||
"integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
"yaml": "bin.mjs"
|
||||
|
||||
@@ -7,7 +7,7 @@ import type { MonitorType } from "@/Types/Monitor";
|
||||
import { Typography, useTheme } from "@mui/material";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const types = ["http", "ping", "port", "docker", "game", "grpc"];
|
||||
const types = ["http", "ping", "port", "docker", "game", "grpc", "websocket"];
|
||||
const typeDisplayNames: Record<string, string> = {
|
||||
http: "HTTP",
|
||||
ping: "Ping",
|
||||
@@ -15,6 +15,7 @@ const typeDisplayNames: Record<string, string> = {
|
||||
docker: "Docker",
|
||||
game: "Game",
|
||||
grpc: "gRPC",
|
||||
websocket: "WebSocket",
|
||||
};
|
||||
const statuses = ["up", "down"];
|
||||
const states = ["active", "paused"];
|
||||
|
||||
@@ -40,8 +40,6 @@ export const useMonitorForm = ({
|
||||
matchMethod: data?.matchMethod || "",
|
||||
expectedValue: data?.expectedValue || "",
|
||||
jsonPath: data?.jsonPath || "",
|
||||
basicAuthUser: data?.basicAuthUser || "",
|
||||
basicAuthPassword: data?.basicAuthPassword || "",
|
||||
};
|
||||
break;
|
||||
case "ping":
|
||||
@@ -85,6 +83,14 @@ export const useMonitorForm = ({
|
||||
ignoreTlsErrors: data?.ignoreTlsErrors || false,
|
||||
};
|
||||
break;
|
||||
case "websocket":
|
||||
defaults = {
|
||||
...base,
|
||||
type: "websocket",
|
||||
url: data?.url || "",
|
||||
ignoreTlsErrors: data?.ignoreTlsErrors || false,
|
||||
};
|
||||
break;
|
||||
case "pagespeed":
|
||||
defaults = {
|
||||
...base,
|
||||
@@ -115,8 +121,6 @@ export const useMonitorForm = ({
|
||||
matchMethod: "",
|
||||
expectedValue: "",
|
||||
jsonPath: "",
|
||||
basicAuthUser: "",
|
||||
basicAuthPassword: "",
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -119,6 +119,17 @@ const getGeneralSettingsConfig = (
|
||||
showGrpcServiceName: true,
|
||||
showIgnoreTls: true,
|
||||
},
|
||||
websocket: {
|
||||
urlLabel: t("pages.createMonitor.form.general.option.wsUrl.label"),
|
||||
urlPlaceholder: t("pages.createMonitor.form.general.option.wsUrl.placeholder"),
|
||||
namePlaceholder: t("pages.createMonitor.form.general.option.name.placeholder"),
|
||||
showUrl: true,
|
||||
showPort: false,
|
||||
showGameSelect: false,
|
||||
showSecret: false,
|
||||
showGrpcServiceName: false,
|
||||
showIgnoreTls: true,
|
||||
},
|
||||
pagespeed: {
|
||||
urlLabel: t("pages.createMonitor.form.general.option.url.label"),
|
||||
urlPlaceholder: t("pages.createMonitor.form.general.option.url.placeholder"),
|
||||
@@ -326,6 +337,13 @@ const CreateMonitorPage = () => {
|
||||
"pages.createMonitor.form.type.optionGrpcDescription"
|
||||
)}
|
||||
/>
|
||||
<RadioWithDescription
|
||||
value="websocket"
|
||||
label={t("pages.createMonitor.form.type.optionWebSocket")}
|
||||
description={t(
|
||||
"pages.createMonitor.form.type.optionWebSocketDescription"
|
||||
)}
|
||||
/>
|
||||
</RadioGroup>
|
||||
</FormControl>
|
||||
)}
|
||||
@@ -742,7 +760,9 @@ const CreateMonitorPage = () => {
|
||||
}
|
||||
/>
|
||||
|
||||
{(watchedType === "http" || watchedType === "grpc") && (
|
||||
{(watchedType === "http" ||
|
||||
watchedType === "grpc" ||
|
||||
watchedType === "websocket") && (
|
||||
<ConfigBox
|
||||
title={t("pages.createMonitor.form.ignoreTls.title")}
|
||||
subtitle={t("pages.createMonitor.form.ignoreTls.description")}
|
||||
@@ -770,50 +790,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" && (
|
||||
<ConfigBox
|
||||
title={t("pages.createMonitor.form.advanced.title")}
|
||||
|
||||
@@ -70,7 +70,7 @@ const UptimeMonitorsPage = () => {
|
||||
const effectiveTypes =
|
||||
selectedTypes.length > 0
|
||||
? selectedTypes
|
||||
: ["http", "ping", "docker", "port", "game", "grpc"];
|
||||
: ["http", "ping", "docker", "port", "game", "grpc", "websocket"];
|
||||
|
||||
// Build URL for monitors with checks
|
||||
const monitorsWithChecksUrl = useMemo(() => {
|
||||
|
||||
@@ -12,6 +12,7 @@ export const MonitorTypes = [
|
||||
"port",
|
||||
"game",
|
||||
"grpc",
|
||||
"websocket",
|
||||
"unknown",
|
||||
] as const;
|
||||
export type MonitorType = (typeof MonitorTypes)[number];
|
||||
@@ -44,8 +45,6 @@ export interface Monitor {
|
||||
jsonPath?: string;
|
||||
expectedValue?: string;
|
||||
matchMethod?: MonitorMatchMethod;
|
||||
basicAuthUser?: string;
|
||||
basicAuthPassword?: string;
|
||||
url: string;
|
||||
port?: number;
|
||||
isActive: boolean;
|
||||
|
||||
@@ -9,6 +9,7 @@ export const getMonitorPath = (type: MonitorType): string => {
|
||||
ping: "uptime",
|
||||
game: "uptime",
|
||||
grpc: "uptime",
|
||||
websocket: "uptime",
|
||||
unknown: "uptime",
|
||||
docker: "uptime",
|
||||
hardware: "infrastructure",
|
||||
|
||||
@@ -38,8 +38,6 @@ const httpSchema = baseSchema.extend({
|
||||
matchMethod: z.enum(["equal", "include", "regex", ""]).optional(),
|
||||
expectedValue: z.string().optional(),
|
||||
jsonPath: z.string().optional(),
|
||||
basicAuthUser: z.string().optional(),
|
||||
basicAuthPassword: z.string().optional(),
|
||||
});
|
||||
|
||||
// Ping monitor schema
|
||||
@@ -87,6 +85,13 @@ const grpcSchema = baseSchema.extend({
|
||||
ignoreTlsErrors: z.boolean(),
|
||||
});
|
||||
|
||||
// WebSocket monitor schema
|
||||
const websocketSchema = baseSchema.extend({
|
||||
type: z.literal("websocket"),
|
||||
url: z.string().min(1, "WebSocket URL is required"),
|
||||
ignoreTlsErrors: z.boolean(),
|
||||
});
|
||||
|
||||
// PageSpeed monitor schema
|
||||
const pagespeedSchema = baseSchema.extend({
|
||||
type: z.literal("pagespeed"),
|
||||
@@ -125,6 +130,7 @@ export const monitorSchema = z.discriminatedUnion("type", [
|
||||
dockerSchema,
|
||||
gameSchema,
|
||||
grpcSchema,
|
||||
websocketSchema,
|
||||
pagespeedSchema,
|
||||
hardwareSchema,
|
||||
]);
|
||||
@@ -139,6 +145,7 @@ export {
|
||||
dockerSchema,
|
||||
gameSchema,
|
||||
grpcSchema,
|
||||
websocketSchema,
|
||||
pagespeedSchema,
|
||||
hardwareSchema,
|
||||
};
|
||||
@@ -487,6 +487,10 @@
|
||||
"grpcServiceName": {
|
||||
"label": "Service name",
|
||||
"placeholder": "e.g. my.service.v1 (leave empty for overall health)"
|
||||
},
|
||||
"wsUrl": {
|
||||
"label": "WebSocket URL",
|
||||
"placeholder": "wss://example.com/socket"
|
||||
}
|
||||
},
|
||||
"title": "General settings",
|
||||
@@ -498,21 +502,10 @@
|
||||
"game": "Enter the IP address or hostname and the port number to ping (e.g., 192.168.1.100 or example.com) and choose game type.",
|
||||
"pagespeed": "Track page load performance, Core Web Vitals, and optimization scores for your website.",
|
||||
"grpc": "Enter the hostname and port of the gRPC server, optionally specify a service name for the health check, and add a display name.",
|
||||
"websocket": "Enter the WebSocket URL to monitor for connection health and response time, and add a display name.",
|
||||
"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": {
|
||||
"description": "Configure TLS/SSL certificate validation for HTTPS connections.",
|
||||
"option": {
|
||||
@@ -546,6 +539,8 @@
|
||||
"optionGameDescription": "Monitor if a specific game server is online.",
|
||||
"optionGrpc": "gRPC",
|
||||
"optionGrpcDescription": "Monitor gRPC services using the standard Health Checking Protocol.",
|
||||
"optionWebSocket": "WebSocket",
|
||||
"optionWebSocketDescription": "Monitor WebSocket endpoints for connection health and response time.",
|
||||
"optionHttp": "HTTP(S)",
|
||||
"optionHttpDescription": "Use HTTP(S) to monitor your website or API endpoint.",
|
||||
"optionPing": "Ping",
|
||||
|
||||
@@ -7,7 +7,7 @@ const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
export default defineConfig(({}) => {
|
||||
let version = "3.5.0";
|
||||
let version = "3.4.0";
|
||||
|
||||
return {
|
||||
base: "/",
|
||||
|
||||
Generated
+16
-3
@@ -43,6 +43,7 @@
|
||||
"super-simple-scheduler": "1.4.5",
|
||||
"swagger-ui-express": "5.0.1",
|
||||
"winston": "^3.13.0",
|
||||
"ws": "^8.19.0",
|
||||
"zod": "^4.3.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -63,6 +64,7 @@
|
||||
"@types/papaparse": "^5.5.2",
|
||||
"@types/ping": "0.4.4",
|
||||
"@types/swagger-ui-express": "4.1.8",
|
||||
"@types/ws": "^8.18.1",
|
||||
"@typescript-eslint/eslint-plugin": "^8.56.1",
|
||||
"@typescript-eslint/parser": "^8.56.1",
|
||||
"c8": "10.1.3",
|
||||
@@ -4673,6 +4675,16 @@
|
||||
"@types/webidl-conversions": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/ws": {
|
||||
"version": "8.18.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz",
|
||||
"integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/yargs": {
|
||||
"version": "17.0.35",
|
||||
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz",
|
||||
@@ -4735,6 +4747,7 @@
|
||||
"integrity": "sha512-klQbnPAAiGYFyI02+znpBRLyjL4/BrBd0nyWkdC0s/6xFLkXYQ8OoRrSkqacS1ddVxf/LDyODIKbQ5TgKAf/Fg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@typescript-eslint/scope-manager": "8.56.1",
|
||||
"@typescript-eslint/types": "8.56.1",
|
||||
@@ -15615,9 +15628,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/ws": {
|
||||
"version": "8.18.3",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz",
|
||||
"integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==",
|
||||
"version": "8.19.0",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz",
|
||||
"integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
|
||||
@@ -58,6 +58,7 @@
|
||||
"super-simple-scheduler": "1.4.5",
|
||||
"swagger-ui-express": "5.0.1",
|
||||
"winston": "^3.13.0",
|
||||
"ws": "^8.19.0",
|
||||
"zod": "^4.3.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -78,6 +79,7 @@
|
||||
"@types/papaparse": "^5.5.2",
|
||||
"@types/ping": "0.4.4",
|
||||
"@types/swagger-ui-express": "4.1.8",
|
||||
"@types/ws": "^8.18.1",
|
||||
"@typescript-eslint/eslint-plugin": "^8.56.1",
|
||||
"@typescript-eslint/parser": "^8.56.1",
|
||||
"c8": "10.1.3",
|
||||
|
||||
@@ -3,6 +3,8 @@ import { MonitorModel } from "../dist/db/models/Monitor.js";
|
||||
import { CheckModel } from "../dist/db/models/Check.js";
|
||||
|
||||
const DEFAULT_MONITOR_ID = "000000000000000000000001";
|
||||
const DEFAULT_TEAM_ID = "0000000000000000000000aa";
|
||||
const DEFAULT_USER_ID = "0000000000000000000000bb";
|
||||
const DEFAULT_MONITOR_TYPE = "http";
|
||||
const DEFAULT_TOTAL = 1_000_000;
|
||||
const DEFAULT_BATCH_SIZE = 5_000;
|
||||
@@ -10,7 +12,7 @@ const DEFAULT_BATCH_SIZE = 5_000;
|
||||
const parseObjectId = (value, fallback) => {
|
||||
try {
|
||||
return new mongoose.Types.ObjectId(value || fallback);
|
||||
} catch {
|
||||
} catch (error) {
|
||||
console.warn(`Invalid ObjectId '${value}', falling back to '${fallback}'.`);
|
||||
return new mongoose.Types.ObjectId(fallback);
|
||||
}
|
||||
|
||||
@@ -44,6 +44,7 @@ import { games, GameDig } from "gamedig";
|
||||
import jmespath from "jmespath";
|
||||
import * as grpc from "@grpc/grpc-js";
|
||||
import * as protoLoader from "@grpc/proto-loader";
|
||||
import WebSocket from "ws";
|
||||
|
||||
// repositories
|
||||
import {
|
||||
@@ -158,7 +159,8 @@ export const initializeServices = async ({
|
||||
net,
|
||||
settingsService,
|
||||
grpc,
|
||||
protoLoader
|
||||
protoLoader,
|
||||
WebSocket
|
||||
);
|
||||
const emailService = new EmailService(settingsService, fs, path, compile, mjml2html, nodemailer, logger);
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ import { requireTeamId, requireUserId } from "@/controllers/controllerUtils.js";
|
||||
import { IStatusPageService } from "@/service/business/statusPageService.js";
|
||||
import { IMonitorsRepository } from "@/repositories/index.js";
|
||||
import { ISettingsService } from "@/service/system/settingsService.js";
|
||||
import { ParseBoolean } from "@/utils/utils.js";
|
||||
import { NormalizeData } from "@/utils/dataUtils.js";
|
||||
|
||||
const SERVICE_NAME = "statusPageController";
|
||||
@@ -109,7 +110,6 @@ class StatusPageController {
|
||||
const normalizedMonitors = sortedMonitors.map((monitor) => {
|
||||
const normalizedChecks = NormalizeData(monitor.recentChecks, 10, 100);
|
||||
if (!showURL) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const { url, port, secret, notifications, ...rest } = monitor;
|
||||
return { ...rest, checks: normalizedChecks };
|
||||
}
|
||||
|
||||
@@ -93,12 +93,6 @@ const MonitorSchema = new Schema<MonitorDocument>(
|
||||
type: String,
|
||||
enum: ["equal", "include", "regex", ""],
|
||||
},
|
||||
basicAuthUser: {
|
||||
type: String,
|
||||
},
|
||||
basicAuthPassword: {
|
||||
type: String,
|
||||
},
|
||||
url: {
|
||||
type: String,
|
||||
required: true,
|
||||
|
||||
@@ -36,7 +36,7 @@ class MongoInvitesRepository implements IInvitesRepository {
|
||||
create = async (invite: Partial<Invite>) => {
|
||||
await InviteModel.deleteMany({ email: invite.email });
|
||||
invite.token = crypto.randomBytes(32).toString("hex");
|
||||
const inviteToken = await InviteModel.create(invite);
|
||||
let inviteToken = await InviteModel.create(invite);
|
||||
return this.toEntity(inviteToken);
|
||||
};
|
||||
|
||||
|
||||
@@ -92,7 +92,7 @@ class MongoMaintenanceWindowsRepository implements IMaintenanceWindowsRepository
|
||||
}
|
||||
|
||||
// Sorting
|
||||
const sort: Record<string, any> = {};
|
||||
let sort: Record<string, any> = {};
|
||||
if (field !== undefined && order !== undefined) {
|
||||
sort[field] = order === "asc" ? 1 : -1;
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ class MongoSettingsRepository implements ISettingsRepository {
|
||||
};
|
||||
|
||||
findSingleton = async () => {
|
||||
const settings = await AppSettingsModel.findOne({ singleton: true }).select("-__v -_id -createdAt -updatedAt -singleton").lean();
|
||||
let settings = await AppSettingsModel.findOne({ singleton: true }).select("-__v -_id -createdAt -updatedAt -singleton").lean();
|
||||
if (!settings) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Types } from "mongoose";
|
||||
import { IChecksRepository, IMonitorsRepository } from "@/repositories/index.js";
|
||||
import type { MonitorStatusResponse, CheckErrorInfo, Check, ILighthouseAudit, ChecksQueryResult, ChecksSummary } from "@/types/index.js";
|
||||
import type { MonitorStatusResponse, CheckErrorInfo, Check } from "@/types/index.js";
|
||||
import type { HardwareStatusPayload, PageSpeedStatusPayload } from "@/types/network.js";
|
||||
import { AppError } from "@/utils/AppError.js";
|
||||
import { ParseBoolean } from "@/utils/utils.js";
|
||||
@@ -8,23 +8,12 @@ import { ILogger } from "@/utils/logger.js";
|
||||
|
||||
const SERVICE_NAME = "checkService";
|
||||
|
||||
export interface ICheckService {
|
||||
createChecks(checks: Check[]): Promise<Check[]>;
|
||||
buildCheck(statusResponse: MonitorStatusResponse<PageSpeedStatusPayload | HardwareStatusPayload | undefined>): Partial<Check> | undefined;
|
||||
getChecksByMonitor(params: { monitorId: string; query: Record<string, string>; teamId: string }): Promise<ChecksQueryResult>;
|
||||
getChecksByTeam(params: { teamId: string; query: Record<string, string> }): Promise<ChecksQueryResult>;
|
||||
getChecksSummaryByTeamId(params: { teamId: string; dateRange: string }): Promise<ChecksSummary>;
|
||||
deleteChecks(params: { monitorId: string; teamId: string }): Promise<number>;
|
||||
deleteChecksByTeamId(params: { teamId: string }): Promise<number>;
|
||||
updateChecksTTL(params: { teamId: string; ttl: string }): Promise<void>;
|
||||
}
|
||||
|
||||
class CheckService implements ICheckService {
|
||||
class CheckService {
|
||||
static SERVICE_NAME = SERVICE_NAME;
|
||||
|
||||
private monitorsRepository: IMonitorsRepository;
|
||||
private checksRepository: IChecksRepository;
|
||||
private logger: ILogger;
|
||||
private logger: any;
|
||||
constructor(monitorsRepository: IMonitorsRepository, logger: ILogger, checksRepository: IChecksRepository) {
|
||||
this.monitorsRepository = monitorsRepository;
|
||||
this.logger = logger;
|
||||
@@ -64,12 +53,13 @@ class CheckService implements ICheckService {
|
||||
message: "Failed to build check",
|
||||
service: SERVICE_NAME,
|
||||
method: "buildCheck",
|
||||
details: "empty payload",
|
||||
});
|
||||
return undefined;
|
||||
}
|
||||
const categories = pageSpeedPayload.lighthouseResult?.categories ?? {};
|
||||
const audits = pageSpeedPayload.lighthouseResult?.audits ?? {};
|
||||
const mapAudit = (audit: ILighthouseAudit | undefined) => {
|
||||
const mapAudit = (audit: any) => {
|
||||
if (!audit || typeof audit !== "object") {
|
||||
return undefined;
|
||||
}
|
||||
@@ -112,7 +102,7 @@ class CheckService implements ICheckService {
|
||||
return check;
|
||||
};
|
||||
|
||||
getChecksByMonitor = async ({ monitorId, query, teamId }: { monitorId: string; query: Record<string, string>; teamId: string }) => {
|
||||
getChecksByMonitor = async ({ monitorId, query, teamId }: { monitorId: string; query: any; teamId: string }) => {
|
||||
if (!monitorId) {
|
||||
throw new AppError({ message: "No monitor ID in request", service: SERVICE_NAME, method: "getChecksByMonitor", status: 400 });
|
||||
}
|
||||
@@ -120,34 +110,28 @@ class CheckService implements ICheckService {
|
||||
throw new AppError({ message: "No team ID in request", service: SERVICE_NAME, method: "getChecksByMonitor", status: 400 });
|
||||
}
|
||||
|
||||
// For verification, throws an error if monitor doesn't belong to team
|
||||
// For verificaiton, throws an error if monitor doesn't belong to team
|
||||
await this.monitorsRepository.findById(monitorId, teamId);
|
||||
|
||||
const { sortOrder, dateRange, filter, page, rowsPerPage, status } = query;
|
||||
if (!sortOrder || !dateRange || !filter || !page || !rowsPerPage) {
|
||||
throw new AppError({ message: "Missing required query parameters", service: SERVICE_NAME, method: "getChecksByMonitor", status: 400 });
|
||||
}
|
||||
let { sortOrder, dateRange, filter, page, rowsPerPage, status } = query;
|
||||
const parsedStatus = typeof status === "undefined" ? status : ParseBoolean(status);
|
||||
const parsedPage = page ? parseInt(page) : 0;
|
||||
const parsedRowsPerPage = rowsPerPage ? parseInt(rowsPerPage) : 5;
|
||||
const parsedPage = page ? parseInt(page) : page;
|
||||
const parsedRowsPerPage = rowsPerPage ? parseInt(rowsPerPage) : rowsPerPage;
|
||||
|
||||
const result = await this.checksRepository.findByMonitorId(monitorId, sortOrder, dateRange, filter, parsedPage, parsedRowsPerPage, parsedStatus);
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
getChecksByTeam = async ({ teamId, query }: { teamId: string; query: Record<string, string> }) => {
|
||||
const { sortOrder, dateRange, filter, page, rowsPerPage } = query;
|
||||
if (!sortOrder || !dateRange || !filter || !page || !rowsPerPage) {
|
||||
throw new AppError({ message: "Missing required query parameters", service: SERVICE_NAME, method: "getChecksByTeam", status: 400 });
|
||||
}
|
||||
getChecksByTeam = async ({ teamId, query }: { teamId: string; query: any }) => {
|
||||
let { sortOrder, dateRange, filter, page, rowsPerPage } = query;
|
||||
|
||||
if (!teamId) {
|
||||
throw new AppError({ message: "No team ID in request", service: SERVICE_NAME, method: "getChecksByTeam", status: 400 });
|
||||
}
|
||||
|
||||
const parsedPage = page ? parseInt(page) : 0;
|
||||
const parsedRowsPerPage = rowsPerPage ? parseInt(rowsPerPage) : 5;
|
||||
const parsedPage = page ? parseInt(page) : page;
|
||||
const parsedRowsPerPage = rowsPerPage ? parseInt(rowsPerPage) : rowsPerPage;
|
||||
|
||||
const checkData = await this.checksRepository.findByTeamId(sortOrder, dateRange, filter, parsedPage, parsedRowsPerPage, teamId);
|
||||
return checkData;
|
||||
@@ -170,7 +154,7 @@ class CheckService implements ICheckService {
|
||||
throw new AppError({ message: "No team ID in request", service: SERVICE_NAME, method: "deleteChecks", status: 400 });
|
||||
}
|
||||
|
||||
// For verification, throws an error if monitor doesn't belong to team
|
||||
// For verificaiton, throws an error if monitor doesn't belong to team
|
||||
await this.monitorsRepository.findById(monitorId, teamId);
|
||||
|
||||
const deletedCount = await this.checksRepository.deleteByMonitorId(monitorId);
|
||||
@@ -186,7 +170,7 @@ class CheckService implements ICheckService {
|
||||
};
|
||||
|
||||
updateChecksTTL = async ({ teamId, ttl }: { teamId: string; ttl: string }) => {
|
||||
throw new AppError({ message: "Not implemented", service: SERVICE_NAME, method: "updateChecksTTL", status: 500, details: { teamId, ttl } });
|
||||
throw new AppError({ message: "Not implemented", service: SERVICE_NAME, method: "updateChecksTTL", status: 500 });
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -170,7 +170,7 @@ class GeoChecksService implements IGeoChecksService {
|
||||
});
|
||||
}
|
||||
|
||||
const { sortOrder, dateRange, page, rowsPerPage, continent } = query;
|
||||
let { sortOrder, dateRange, page, rowsPerPage, continent } = query;
|
||||
const continents = continent ? (Array.isArray(continent) ? continent : [continent]) : undefined;
|
||||
|
||||
this.logger.debug({
|
||||
|
||||
@@ -226,7 +226,8 @@ export class MonitorService implements IMonitorService {
|
||||
checksData.monitorType !== "docker" &&
|
||||
checksData.monitorType !== "port" &&
|
||||
checksData.monitorType !== "game" &&
|
||||
checksData.monitorType !== "grpc"
|
||||
checksData.monitorType !== "grpc" &&
|
||||
checksData.monitorType !== "websocket"
|
||||
) {
|
||||
throw new AppError({ message: `${monitor.type} monitors are not supported for uptime details`, status: 400 });
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ const SERVICE_NAME = "JobQueueHelper";
|
||||
import type { Monitor } from "@/types/monitor.js";
|
||||
import { AppError } from "@/utils/AppError.js";
|
||||
import { INetworkService, INotificationsService, IStatusService } from "@/service/index.js";
|
||||
import type { StatusChangeResult } from "@/types/index.js";
|
||||
import type { StatusChangeResult, MonitorStatusResponse, HardwareStatusPayload, MonitorStatus } from "@/types/index.js";
|
||||
import IncidentService from "@/service/business/incidentService.js";
|
||||
import type { IGeoChecksService } from "@/service/business/geoChecksService.js";
|
||||
import {
|
||||
|
||||
@@ -3,7 +3,6 @@ import type { GeoCheck } from "@/types/index.js";
|
||||
import type { IGeoChecksService } from "../business/geoChecksService.js";
|
||||
import type { ILogger } from "@/utils/logger.js";
|
||||
import type { ISettingsService } from "@/service/system/settingsService.js";
|
||||
import { ICheckService } from "../business/checkService.js";
|
||||
const SERVICE_NAME = "BufferService";
|
||||
|
||||
export interface IBufferService {
|
||||
@@ -20,13 +19,13 @@ class BufferService implements IBufferService {
|
||||
private BUFFER_TIMEOUT: number;
|
||||
private logger: ILogger;
|
||||
private SERVICE_NAME: string;
|
||||
private buffer: Check[];
|
||||
private geoBuffer: GeoCheck[];
|
||||
private buffer: any[];
|
||||
private geoBuffer: any[];
|
||||
private bufferTimer: NodeJS.Timeout | null = null;
|
||||
private checksService: ICheckService;
|
||||
private checksService: any;
|
||||
private geoChecksService: IGeoChecksService;
|
||||
|
||||
constructor(logger: ILogger, checkService: ICheckService, geoChecksService: IGeoChecksService, settingsService: ISettingsService) {
|
||||
constructor(logger: ILogger, checkService: any, geoChecksService: IGeoChecksService, settingsService: ISettingsService) {
|
||||
this.BUFFER_TIMEOUT = settingsService.getSettings().nodeEnv === "development" ? 10 : 1000 * 60 * 1; // 1 minute
|
||||
this.logger = logger;
|
||||
this.checksService = checkService;
|
||||
@@ -49,12 +48,12 @@ class BufferService implements IBufferService {
|
||||
addToBuffer(check: Check) {
|
||||
try {
|
||||
this.buffer.push(check);
|
||||
} catch (error: unknown) {
|
||||
} catch (error: any) {
|
||||
this.logger.error({
|
||||
message: error instanceof Error ? error.message : "Unknown error",
|
||||
message: error.message,
|
||||
service: this.SERVICE_NAME,
|
||||
method: "addToBuffer",
|
||||
stack: error instanceof Error ? error.stack : undefined,
|
||||
stack: error.stack,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -62,12 +61,12 @@ class BufferService implements IBufferService {
|
||||
addGeoCheckToBuffer(geoCheck: GeoCheck) {
|
||||
try {
|
||||
this.geoBuffer.push(geoCheck);
|
||||
} catch (error: unknown) {
|
||||
} catch (error: any) {
|
||||
this.logger.error({
|
||||
message: error instanceof Error ? error.message : "Unknown error",
|
||||
message: error.message,
|
||||
service: this.SERVICE_NAME,
|
||||
method: "addGeoCheckToBuffer",
|
||||
stack: error instanceof Error ? error.stack : undefined,
|
||||
stack: error.stack,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -99,12 +98,12 @@ class BufferService implements IBufferService {
|
||||
}
|
||||
|
||||
return false;
|
||||
} catch (error: unknown) {
|
||||
} catch (error: any) {
|
||||
this.logger.error({
|
||||
message: error instanceof Error ? error.message : "Unknown error",
|
||||
message: error.message,
|
||||
service: this.SERVICE_NAME,
|
||||
method: "removeCheckFromBuffer",
|
||||
stack: error instanceof Error ? error.stack : undefined,
|
||||
stack: error.stack,
|
||||
});
|
||||
return false;
|
||||
}
|
||||
@@ -118,12 +117,12 @@ class BufferService implements IBufferService {
|
||||
try {
|
||||
await this.flushBuffer();
|
||||
await this.flushGeoBuffer();
|
||||
} catch (error: unknown) {
|
||||
} catch (error: any) {
|
||||
this.logger.error({
|
||||
message: error instanceof Error ? error.message : "Unknown error",
|
||||
message: `Error in flush cycle: ${error.message}`,
|
||||
service: this.SERVICE_NAME,
|
||||
method: "scheduleNextFlush",
|
||||
stack: error instanceof Error ? error.stack : undefined,
|
||||
stack: error.stack,
|
||||
});
|
||||
} finally {
|
||||
// Schedule the next flush only after the current one completes
|
||||
@@ -142,12 +141,12 @@ class BufferService implements IBufferService {
|
||||
await this.checksService.createChecks(this.buffer);
|
||||
this.buffer = [];
|
||||
}
|
||||
} catch (error: unknown) {
|
||||
} catch (error: any) {
|
||||
this.logger.error({
|
||||
message: error instanceof Error ? error.message : "Unknown error",
|
||||
message: `Error flushing checks buffer: ${error.message}`,
|
||||
service: this.SERVICE_NAME,
|
||||
method: "flushBuffer",
|
||||
stack: error instanceof Error ? error.stack : undefined,
|
||||
stack: error.stack,
|
||||
});
|
||||
// Clear buffer even on error to prevent infinite retry loops
|
||||
this.buffer = [];
|
||||
@@ -165,12 +164,12 @@ class BufferService implements IBufferService {
|
||||
await this.geoChecksService.createGeoChecks(this.geoBuffer);
|
||||
this.geoBuffer = [];
|
||||
}
|
||||
} catch (error: unknown) {
|
||||
} catch (error: any) {
|
||||
this.logger.error({
|
||||
message: error instanceof Error ? error.message : "Unknown error",
|
||||
message: `Error flushing geo checks buffer: ${error.message}`,
|
||||
service: this.SERVICE_NAME,
|
||||
method: "flushGeoBuffer",
|
||||
stack: error instanceof Error ? error.stack : undefined,
|
||||
stack: error.stack,
|
||||
});
|
||||
// Clear buffer even on error to prevent infinite retry loops
|
||||
this.geoBuffer = [];
|
||||
|
||||
@@ -1,51 +1,27 @@
|
||||
import { fileURLToPath } from "url";
|
||||
import path from "path";
|
||||
import { EmailTransportConfig } from "@/types/index.js";
|
||||
import { ISettingsService } from "@/service/system/settingsService.js";
|
||||
import { ILogger } from "@/utils/logger.js";
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import nodemailer from "nodemailer";
|
||||
import mjml2html from "mjml";
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
const SERVICE_NAME = "EmailService";
|
||||
type MjmlFn = typeof mjml2html;
|
||||
type FileSystem = typeof fs;
|
||||
type PathModule = typeof path;
|
||||
type Mailer = typeof nodemailer;
|
||||
type TemplateCompiler = (template: string) => (context: Record<string, unknown>) => string;
|
||||
|
||||
export interface IEmailService {
|
||||
init(): void;
|
||||
buildEmail(template: string, context: Record<string, unknown>): Promise<string | undefined>;
|
||||
sendEmail(to: string, subject: string, html: string, transportConfig?: EmailTransportConfig): Promise<string | false | undefined>;
|
||||
}
|
||||
|
||||
class EmailService implements IEmailService {
|
||||
class EmailService {
|
||||
static SERVICE_NAME = SERVICE_NAME;
|
||||
|
||||
private settingsService: ISettingsService;
|
||||
private fs: FileSystem;
|
||||
private path: PathModule;
|
||||
private compile: TemplateCompiler;
|
||||
private mjml2html: MjmlFn;
|
||||
private nodemailer: Mailer;
|
||||
private logger: ILogger;
|
||||
private transporter: ReturnType<typeof import("nodemailer").createTransport> | null = null;
|
||||
private templateLookup: Record<string, ((context: Record<string, unknown>) => string) | undefined>;
|
||||
private loadTemplate: (templateName: string) => ((context: Record<string, unknown>) => string) | undefined;
|
||||
private settingsService: any;
|
||||
private fs: any;
|
||||
private path: any;
|
||||
private compile: any;
|
||||
private mjml2html: any;
|
||||
private nodemailer: any;
|
||||
private logger: any;
|
||||
private transporter: any;
|
||||
private templateLookup: Record<string, Function>;
|
||||
private loadTemplate: (templateName: string) => Function;
|
||||
|
||||
constructor(
|
||||
settingsService: ISettingsService,
|
||||
fs: FileSystem,
|
||||
path: PathModule,
|
||||
compile: TemplateCompiler,
|
||||
mjml2html: MjmlFn,
|
||||
nodemailer: Mailer,
|
||||
logger: ILogger
|
||||
) {
|
||||
constructor(settingsService: any, fs: any, path: any, compile: any, mjml2html: any, nodemailer: any, logger: any) {
|
||||
this.settingsService = settingsService;
|
||||
this.fs = fs;
|
||||
this.path = path;
|
||||
@@ -54,7 +30,9 @@ class EmailService implements IEmailService {
|
||||
this.nodemailer = nodemailer;
|
||||
this.logger = logger;
|
||||
this.templateLookup = {};
|
||||
this.loadTemplate = () => undefined;
|
||||
this.loadTemplate = () => {
|
||||
return () => {};
|
||||
};
|
||||
this.init();
|
||||
}
|
||||
|
||||
@@ -68,12 +46,12 @@ class EmailService implements IEmailService {
|
||||
const templatePath = this.path.join(__dirname, `../../templates/${templateName}.mjml`);
|
||||
const templateContent = this.fs.readFileSync(templatePath, "utf8");
|
||||
return this.compile(templateContent);
|
||||
} catch (error: unknown) {
|
||||
} catch (error: any) {
|
||||
this.logger.error({
|
||||
message: error instanceof Error ? error.message : "Unknown error",
|
||||
message: error.message,
|
||||
service: SERVICE_NAME,
|
||||
method: "loadTemplate",
|
||||
stack: error instanceof Error ? error.stack : undefined,
|
||||
stack: error.stack,
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -88,20 +66,17 @@ class EmailService implements IEmailService {
|
||||
};
|
||||
};
|
||||
|
||||
buildEmail = async (template: string, context: Record<string, unknown>) => {
|
||||
buildEmail = async (template: string, context: Record<string, any>) => {
|
||||
try {
|
||||
const mjml = this.templateLookup[template]?.(context);
|
||||
if (!mjml) {
|
||||
throw new Error(`Template ${template} not found`);
|
||||
}
|
||||
const html = this.mjml2html(mjml);
|
||||
const html = await this.mjml2html(mjml);
|
||||
return html.html;
|
||||
} catch (error: unknown) {
|
||||
} catch (error: any) {
|
||||
this.logger.error({
|
||||
message: error instanceof Error ? error.message : "Unknown error",
|
||||
message: error.message,
|
||||
service: SERVICE_NAME,
|
||||
method: "buildEmail",
|
||||
stack: error instanceof Error ? error.stack : undefined,
|
||||
stack: error.stack,
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -150,12 +125,11 @@ class EmailService implements IEmailService {
|
||||
|
||||
try {
|
||||
await this.transporter.verify();
|
||||
} catch (error: unknown) {
|
||||
} catch (error) {
|
||||
this.logger.warn({
|
||||
message: "Email transporter verification failed",
|
||||
service: SERVICE_NAME,
|
||||
method: "verifyTransporter",
|
||||
stack: error instanceof Error ? error.stack : undefined,
|
||||
});
|
||||
return false;
|
||||
}
|
||||
@@ -168,12 +142,12 @@ class EmailService implements IEmailService {
|
||||
html: html,
|
||||
});
|
||||
return info?.messageId;
|
||||
} catch (error: unknown) {
|
||||
} catch (error: any) {
|
||||
this.logger.error({
|
||||
message: error instanceof Error ? error.message : "Unknown error",
|
||||
message: error.message,
|
||||
service: SERVICE_NAME,
|
||||
method: "sendEmail",
|
||||
stack: error instanceof Error ? error.stack : undefined,
|
||||
stack: error.stack,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -95,12 +95,12 @@ class GlobalPingService implements IGlobalPingService {
|
||||
});
|
||||
|
||||
return measurementId;
|
||||
} catch (error: unknown) {
|
||||
} catch (error: any) {
|
||||
this.logger.error({
|
||||
message: "GlobalPing API unavailable, skipping geo check",
|
||||
service: SERVICE_NAME,
|
||||
method: "createMeasurement",
|
||||
stack: error instanceof Error ? error.stack : undefined,
|
||||
details: error,
|
||||
});
|
||||
return null;
|
||||
}
|
||||
@@ -140,12 +140,12 @@ class GlobalPingService implements IGlobalPingService {
|
||||
|
||||
// Still in-progress, wait and poll again
|
||||
await this.sleep(POLL_INTERVAL_MS);
|
||||
} catch (error: unknown) {
|
||||
} catch (error: any) {
|
||||
this.logger.error({
|
||||
message: "Error polling GlobalPing API",
|
||||
service: SERVICE_NAME,
|
||||
method: "pollForResults",
|
||||
stack: error instanceof Error ? error.stack : undefined,
|
||||
details: error.message,
|
||||
});
|
||||
return [];
|
||||
}
|
||||
|
||||
@@ -77,6 +77,7 @@ class NetworkService implements INetworkService {
|
||||
private TYPE_PORT: string;
|
||||
private TYPE_GAME: string;
|
||||
private TYPE_GRPC: string;
|
||||
private TYPE_WEBSOCKET: string;
|
||||
private SERVICE_NAME: string;
|
||||
private NETWORK_ERROR: number;
|
||||
private PING_ERROR: number;
|
||||
@@ -91,6 +92,7 @@ class NetworkService implements INetworkService {
|
||||
private Docker: typeof import("dockerode");
|
||||
private net: typeof import("net");
|
||||
private settingsService: ISettingsService;
|
||||
private WebSocket: any;
|
||||
private grpc: typeof import("@grpc/grpc-js");
|
||||
private protoLoader: typeof import("@grpc/proto-loader");
|
||||
|
||||
@@ -159,6 +161,7 @@ class NetworkService implements INetworkService {
|
||||
Docker: typeof import("dockerode"),
|
||||
net: typeof import("net"),
|
||||
settingsService: ISettingsService,
|
||||
WebSocket: any,
|
||||
grpc: typeof import("@grpc/grpc-js"),
|
||||
protoLoader: typeof import("@grpc/proto-loader")
|
||||
) {
|
||||
@@ -168,6 +171,7 @@ class NetworkService implements INetworkService {
|
||||
this.TYPE_HARDWARE = "hardware";
|
||||
this.TYPE_DOCKER = "docker";
|
||||
this.TYPE_PORT = "port";
|
||||
this.TYPE_WEBSOCKET = "websocket";
|
||||
this.TYPE_GAME = "game";
|
||||
this.TYPE_GRPC = "grpc";
|
||||
this.SERVICE_NAME = SERVICE_NAME;
|
||||
@@ -184,6 +188,7 @@ class NetworkService implements INetworkService {
|
||||
this.settingsService = settingsService;
|
||||
this.grpc = grpc;
|
||||
this.protoLoader = protoLoader;
|
||||
this.WebSocket = WebSocket;
|
||||
|
||||
const cacheable = new CacheableLookup();
|
||||
|
||||
@@ -229,6 +234,7 @@ class NetworkService implements INetworkService {
|
||||
>
|
||||
> {
|
||||
const type = monitor?.type || "unknown";
|
||||
|
||||
switch (type) {
|
||||
case this.TYPE_PING:
|
||||
return await this.requestPing(monitor);
|
||||
@@ -246,6 +252,8 @@ class NetworkService implements INetworkService {
|
||||
return await this.requestGame(monitor);
|
||||
case this.TYPE_GRPC:
|
||||
return await this.requestGrpc(monitor);
|
||||
case this.TYPE_WEBSOCKET:
|
||||
return await this.requestWebSocket(monitor);
|
||||
default:
|
||||
return this.handleUnsupportedType(type);
|
||||
}
|
||||
@@ -296,7 +304,7 @@ class NetworkService implements INetworkService {
|
||||
}
|
||||
|
||||
private async requestHttp<T = unknown>(monitor: Monitor): Promise<MonitorStatusResponse<T>> {
|
||||
const { url, secret, ignoreTlsErrors, useAdvancedMatching, jsonPath, matchMethod, expectedValue, basicAuthUser, basicAuthPassword } = monitor;
|
||||
const { url, secret, ignoreTlsErrors, useAdvancedMatching, jsonPath, matchMethod, expectedValue } = monitor;
|
||||
const httpResponse = this.buildStatusResponse<T>({
|
||||
monitor,
|
||||
overrides: {
|
||||
@@ -310,15 +318,8 @@ class NetworkService implements INetworkService {
|
||||
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,
|
||||
headers: secret ? { Authorization: `Bearer ${secret}` } : undefined,
|
||||
};
|
||||
|
||||
if (ignoreTlsErrors) {
|
||||
@@ -860,6 +861,72 @@ class NetworkService implements INetworkService {
|
||||
return statusNames[code] || "UNKNOWN";
|
||||
}
|
||||
|
||||
private async requestWebSocket(monitor: Monitor): Promise<MonitorStatusResponse> {
|
||||
try {
|
||||
const { url } = monitor;
|
||||
|
||||
if (!url) {
|
||||
throw new Error("Monitor URL is required");
|
||||
}
|
||||
|
||||
const TIMEOUT_MS = 10000;
|
||||
|
||||
const wsResponse = this.buildStatusResponse({
|
||||
monitor,
|
||||
overrides: {
|
||||
status: false,
|
||||
code: this.NETWORK_ERROR,
|
||||
message: "WebSocket connection not established",
|
||||
},
|
||||
});
|
||||
|
||||
const { response, responseTime, error } = await this.timeRequest<{ connected: boolean }>(() => {
|
||||
return new Promise<{ connected: boolean }>((resolve, reject) => {
|
||||
const wsOptions: Record<string, any> = {};
|
||||
|
||||
const ws = new this.WebSocket(url, wsOptions);
|
||||
|
||||
const timeout = setTimeout(() => {
|
||||
ws.close();
|
||||
reject(new Error("WebSocket connection timeout"));
|
||||
}, TIMEOUT_MS);
|
||||
|
||||
ws.on("open", () => {
|
||||
clearTimeout(timeout);
|
||||
ws.close();
|
||||
resolve({ connected: true });
|
||||
});
|
||||
|
||||
ws.on("error", (err: Error) => {
|
||||
clearTimeout(timeout);
|
||||
ws.close();
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
if (error) {
|
||||
wsResponse.status = false;
|
||||
wsResponse.code = this.NETWORK_ERROR;
|
||||
wsResponse.message = (error as Error).message || "WebSocket connection failed";
|
||||
wsResponse.responseTime = responseTime;
|
||||
return wsResponse;
|
||||
}
|
||||
|
||||
wsResponse.status = true;
|
||||
wsResponse.code = 200;
|
||||
wsResponse.message = "WebSocket connection successful";
|
||||
wsResponse.responseTime = responseTime;
|
||||
wsResponse.payload = response;
|
||||
|
||||
return wsResponse;
|
||||
} catch (err: any) {
|
||||
err.service = this.SERVICE_NAME;
|
||||
err.method = "requestWebSocket";
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
private async handleUnsupportedType(type: string): Promise<MonitorStatusResponse> {
|
||||
return {
|
||||
monitorId: "unknown",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { HardwareStatusPayload, Monitor, MonitorStatusResponse } from "@/types/index.js";
|
||||
import type { Monitor, MonitorStatusResponse } from "@/types/index.js";
|
||||
import type { MonitorActionDecision } from "@/service/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js";
|
||||
import type {
|
||||
NotificationMessage,
|
||||
@@ -30,8 +30,8 @@ export class NotificationMessageBuilder implements INotificationMessageBuilder {
|
||||
clientHost: string
|
||||
): NotificationMessage {
|
||||
const type = this.determineNotificationType(decision, monitor);
|
||||
const severity = this.determineSeverity(type);
|
||||
const content = this.buildContent(type, monitor, monitorStatusResponse);
|
||||
const severity = this.determineSeverity(type, monitor.status);
|
||||
const content = this.buildContent(type, monitor, monitorStatusResponse, decision, clientHost);
|
||||
|
||||
return {
|
||||
type,
|
||||
@@ -77,7 +77,7 @@ export class NotificationMessageBuilder implements INotificationMessageBuilder {
|
||||
return "monitor_up";
|
||||
}
|
||||
|
||||
private determineSeverity(type: NotificationType): NotificationSeverity {
|
||||
private determineSeverity(type: NotificationType, monitorStatus: string): NotificationSeverity {
|
||||
switch (type) {
|
||||
case "monitor_down":
|
||||
return "critical";
|
||||
@@ -93,22 +93,28 @@ export class NotificationMessageBuilder implements INotificationMessageBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
private buildContent(type: NotificationType, monitor: Monitor, monitorStatusResponse: MonitorStatusResponse): NotificationContent {
|
||||
private buildContent(
|
||||
type: NotificationType,
|
||||
monitor: Monitor,
|
||||
monitorStatusResponse: MonitorStatusResponse,
|
||||
decision: MonitorActionDecision,
|
||||
clientHost: string
|
||||
): NotificationContent {
|
||||
switch (type) {
|
||||
case "monitor_down":
|
||||
return this.buildMonitorDownContent(monitor, monitorStatusResponse);
|
||||
return this.buildMonitorDownContent(monitor, monitorStatusResponse, clientHost);
|
||||
case "monitor_up":
|
||||
return this.buildMonitorUpContent(monitor);
|
||||
return this.buildMonitorUpContent(monitor, clientHost);
|
||||
case "threshold_breach":
|
||||
return this.buildThresholdBreachContent(monitor, monitorStatusResponse as MonitorStatusResponse<HardwareStatusPayload>);
|
||||
return this.buildThresholdBreachContent(monitor, monitorStatusResponse, decision, clientHost);
|
||||
case "threshold_resolved":
|
||||
return this.buildThresholdResolvedContent(monitor);
|
||||
return this.buildThresholdResolvedContent(monitor, clientHost);
|
||||
default:
|
||||
return this.buildDefaultContent(monitor);
|
||||
return this.buildDefaultContent(monitor, clientHost);
|
||||
}
|
||||
}
|
||||
|
||||
private buildMonitorDownContent(monitor: Monitor, monitorStatusResponse: MonitorStatusResponse): NotificationContent {
|
||||
private buildMonitorDownContent(monitor: Monitor, monitorStatusResponse: MonitorStatusResponse, clientHost: string): NotificationContent {
|
||||
const title = `Monitor Down: ${monitor.name}`;
|
||||
const summary = `Monitor "${monitor.name}" is currently down and unreachable.`;
|
||||
const details = [`URL: ${monitor.url}`, `Status: Down`, `Type: ${monitor.type}`];
|
||||
@@ -131,7 +137,7 @@ export class NotificationMessageBuilder implements INotificationMessageBuilder {
|
||||
};
|
||||
}
|
||||
|
||||
private buildMonitorUpContent(monitor: Monitor): NotificationContent {
|
||||
private buildMonitorUpContent(monitor: Monitor, clientHost: string): NotificationContent {
|
||||
const title = `Monitor Recovered: ${monitor.name}`;
|
||||
const summary = `Monitor "${monitor.name}" is back up and operational.`;
|
||||
const details = [`URL: ${monitor.url}`, `Status: Up`, `Type: ${monitor.type}`];
|
||||
@@ -144,7 +150,12 @@ export class NotificationMessageBuilder implements INotificationMessageBuilder {
|
||||
};
|
||||
}
|
||||
|
||||
private buildThresholdBreachContent(monitor: Monitor, monitorStatusResponse: MonitorStatusResponse<HardwareStatusPayload>): NotificationContent {
|
||||
private buildThresholdBreachContent(
|
||||
monitor: Monitor,
|
||||
monitorStatusResponse: MonitorStatusResponse,
|
||||
decision: MonitorActionDecision,
|
||||
clientHost: string
|
||||
): NotificationContent {
|
||||
const title = `Threshold Breach: ${monitor.name}`;
|
||||
const summary = `Monitor "${monitor.name}" has breached one or more thresholds.`;
|
||||
const details = [`URL: ${monitor.url}`, `Status: Breached`, `Type: ${monitor.type}`];
|
||||
@@ -160,7 +171,7 @@ export class NotificationMessageBuilder implements INotificationMessageBuilder {
|
||||
};
|
||||
}
|
||||
|
||||
private buildThresholdResolvedContent(monitor: Monitor): NotificationContent {
|
||||
private buildThresholdResolvedContent(monitor: Monitor, clientHost: string): NotificationContent {
|
||||
const title = `Thresholds Resolved: ${monitor.name}`;
|
||||
const summary = `Monitor "${monitor.name}" thresholds have returned to normal.`;
|
||||
const details = [`URL: ${monitor.url}`, `Status: Up`, `Type: ${monitor.type}`];
|
||||
@@ -173,7 +184,7 @@ export class NotificationMessageBuilder implements INotificationMessageBuilder {
|
||||
};
|
||||
}
|
||||
|
||||
private buildDefaultContent(monitor: Monitor): NotificationContent {
|
||||
private buildDefaultContent(monitor: Monitor, clientHost: string): NotificationContent {
|
||||
return {
|
||||
title: `Monitor: ${monitor.name}`,
|
||||
summary: `Status update for monitor "${monitor.name}".`,
|
||||
@@ -182,7 +193,7 @@ export class NotificationMessageBuilder implements INotificationMessageBuilder {
|
||||
};
|
||||
}
|
||||
|
||||
public extractThresholdBreaches(monitor: Monitor, monitorStatusResponse: MonitorStatusResponse<HardwareStatusPayload>): ThresholdBreach[] {
|
||||
public extractThresholdBreaches(monitor: Monitor, monitorStatusResponse: MonitorStatusResponse): ThresholdBreach[] {
|
||||
const breaches: ThresholdBreach[] = [];
|
||||
|
||||
// Check if this is a hardware monitor with threshold data
|
||||
@@ -191,7 +202,7 @@ export class NotificationMessageBuilder implements INotificationMessageBuilder {
|
||||
}
|
||||
|
||||
// Cast to HardwareStatusPayload type
|
||||
const payload = monitorStatusResponse.payload;
|
||||
const payload = monitorStatusResponse.payload as { data?: any };
|
||||
const hardware = payload.data;
|
||||
|
||||
if (!hardware) {
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
const SERVICE_NAME = "DiscordProvider";
|
||||
import type { AlertDiscordPayload, DiscordEmbedField, Notification } from "@/types/index.js";
|
||||
import type { Notification } from "@/types/index.js";
|
||||
import { INotificationProvider } from "@/service/index.js";
|
||||
import { getTestMessage } from "@/service/infrastructure/notificationProviders/utils.js";
|
||||
import type { NotificationMessage, NotificationSeverity } from "@/types/notificationMessage.js";
|
||||
import type { NotificationMessage } from "@/types/notificationMessage.js";
|
||||
import got from "got";
|
||||
import { ILogger } from "@/utils/logger.js";
|
||||
|
||||
export class DiscordProvider implements INotificationProvider {
|
||||
private logger: ILogger;
|
||||
private logger: any;
|
||||
|
||||
constructor(logger: ILogger) {
|
||||
constructor(logger: any) {
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
@@ -37,6 +36,9 @@ export class DiscordProvider implements INotificationProvider {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* New unified message format implementation
|
||||
*/
|
||||
async sendMessage(notification: Notification, message: NotificationMessage): Promise<boolean> {
|
||||
if (!notification.address) {
|
||||
this.logger.warn({
|
||||
@@ -69,17 +71,18 @@ export class DiscordProvider implements INotificationProvider {
|
||||
}
|
||||
}
|
||||
|
||||
private buildDiscordEmbed(message: NotificationMessage): AlertDiscordPayload {
|
||||
const colorMap: Record<NotificationSeverity, number> = {
|
||||
private buildDiscordEmbed(message: NotificationMessage): any {
|
||||
// Map severity to Discord embed colors
|
||||
const colorMap: Record<string, number> = {
|
||||
critical: 0xdc2626, // red-600
|
||||
warning: 0xf59e0b, // amber-500
|
||||
info: 0x3b82f6, // blue-500
|
||||
success: 0x10b981, // green-500
|
||||
};
|
||||
|
||||
const color = colorMap[message.severity] ?? colorMap.info;
|
||||
const color = colorMap[message.severity] || colorMap.info;
|
||||
|
||||
const fields: Array<DiscordEmbedField> = [];
|
||||
const fields: Array<{ name: string; value: string; inline?: boolean }> = [];
|
||||
|
||||
// Add monitor details
|
||||
fields.push({
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
const SERVICE_NAME = "EmailProvider";
|
||||
import type { Notification } from "@/types/index.js";
|
||||
import type { Monitor, Notification, MonitorStatusResponse } from "@/types/index.js";
|
||||
import { INotificationProvider } from "@/service/index.js";
|
||||
import type { MonitorActionDecision } from "@/service/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js";
|
||||
import { buildTestEmail } from "@/service/infrastructure/notificationProviders/utils.js";
|
||||
import type { NotificationMessage } from "@/types/notificationMessage.js";
|
||||
import type { ILogger } from "@/utils/logger.js";
|
||||
import { IEmailService } from "@/service/infrastructure/emailService.js";
|
||||
export class EmailProvider implements INotificationProvider {
|
||||
private emailService: IEmailService;
|
||||
private logger: ILogger;
|
||||
|
||||
constructor(emailService: IEmailService, logger: ILogger) {
|
||||
export class EmailProvider implements INotificationProvider {
|
||||
private emailService: any;
|
||||
private logger: any;
|
||||
|
||||
constructor(emailService: any, logger: any) {
|
||||
this.emailService = emailService;
|
||||
this.logger = logger;
|
||||
}
|
||||
@@ -18,24 +18,6 @@ export class EmailProvider implements INotificationProvider {
|
||||
const subject = "Test notification";
|
||||
const html = await buildTestEmail(this.emailService);
|
||||
|
||||
if (!notification.address) {
|
||||
this.logger.warn({
|
||||
message: "Missing address",
|
||||
service: SERVICE_NAME,
|
||||
method: "sendTestAlert",
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!html) {
|
||||
this.logger.warn({
|
||||
message: "Failed to build test email content",
|
||||
service: SERVICE_NAME,
|
||||
method: "sendTestAlert",
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
const messageId = await this.emailService.sendEmail(notification.address, subject, html);
|
||||
if (!messageId) {
|
||||
this.logger.warn({
|
||||
@@ -56,15 +38,6 @@ export class EmailProvider implements INotificationProvider {
|
||||
const subject = this.buildSubject(message);
|
||||
const html = await this.buildEmailFromMessage(message);
|
||||
|
||||
if (!html) {
|
||||
this.logger.warn({
|
||||
message: "Failed to build email content",
|
||||
service: SERVICE_NAME,
|
||||
method: "sendMessage",
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
const messageId = await this.emailService.sendEmail(notification.address, subject, html);
|
||||
if (!messageId) {
|
||||
this.logger.warn({
|
||||
@@ -92,7 +65,7 @@ export class EmailProvider implements INotificationProvider {
|
||||
}
|
||||
}
|
||||
|
||||
private async buildEmailFromMessage(message: NotificationMessage): Promise<string | undefined> {
|
||||
private async buildEmailFromMessage(message: NotificationMessage): Promise<string> {
|
||||
const context = {
|
||||
title: message.content.title,
|
||||
summary: message.content.summary,
|
||||
@@ -115,6 +88,16 @@ export class EmailProvider implements INotificationProvider {
|
||||
|
||||
const html = await this.emailService.buildEmail("unifiedNotificationTemplate", context);
|
||||
|
||||
this.logger.info({
|
||||
message: "[DEBUG] Email HTML generated",
|
||||
service: SERVICE_NAME,
|
||||
method: "buildEmailFromMessage",
|
||||
details: {
|
||||
htmlLength: html?.length || 0,
|
||||
htmlPreview: html?.substring(0, 200),
|
||||
},
|
||||
});
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
const SERVICE_NAME = "MatrixProvider";
|
||||
import got from "got";
|
||||
import type { INotificationProvider } from "@/service/index.js";
|
||||
import type { AlertMatrixPayload, Notification } from "@/types/index.js";
|
||||
import type { Notification } from "@/types/index.js";
|
||||
import type { NotificationMessage } from "@/types/notificationMessage.js";
|
||||
import { getTestMessage } from "@/service/infrastructure/notificationProviders/utils.js";
|
||||
import { ILogger } from "@/utils/logger.js";
|
||||
|
||||
export class MatrixProvider implements INotificationProvider {
|
||||
private logger: ILogger;
|
||||
private logger: any;
|
||||
|
||||
constructor(logger: ILogger) {
|
||||
constructor(logger: any) {
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
@@ -43,6 +42,9 @@ export class MatrixProvider implements INotificationProvider {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* New unified message format - builds Matrix HTML message from NotificationMessage
|
||||
*/
|
||||
sendMessage = async (notification: Notification, message: NotificationMessage): Promise<boolean> => {
|
||||
const { homeserverUrl, accessToken, roomId } = notification;
|
||||
|
||||
@@ -85,7 +87,7 @@ export class MatrixProvider implements INotificationProvider {
|
||||
* Returns both plain text and HTML formatted versions
|
||||
* Matrix supports HTML subset for rich formatting
|
||||
*/
|
||||
private buildMatrixMessage(message: NotificationMessage): AlertMatrixPayload {
|
||||
private buildMatrixMessage(message: NotificationMessage): { plainText: string; htmlText: string } {
|
||||
const plainLines: string[] = [];
|
||||
const htmlLines: string[] = [];
|
||||
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
const SERVICE_NAME = "PagerDutyProvider";
|
||||
import got from "got";
|
||||
import type { Notification } from "@/types/index.js";
|
||||
import type { Monitor, Notification, MonitorStatusResponse } from "@/types/index.js";
|
||||
import type { MonitorActionDecision } from "@/service/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js";
|
||||
import { INotificationProvider } from "@/service/index.js";
|
||||
import type { NotificationMessage } from "@/types/notificationMessage.js";
|
||||
import { getTestMessage } from "@/service/infrastructure/notificationProviders/utils.js";
|
||||
import { ILogger } from "@/utils/logger.js";
|
||||
import { AlertPagerDutyPayload } from "@/types/index.js";
|
||||
|
||||
export class PagerDutyProvider implements INotificationProvider {
|
||||
private logger: ILogger;
|
||||
private logger: any;
|
||||
|
||||
constructor(logger: ILogger) {
|
||||
constructor(logger: any) {
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
@@ -78,7 +77,7 @@ export class PagerDutyProvider implements INotificationProvider {
|
||||
}
|
||||
}
|
||||
|
||||
private buildPagerDutyPayload(notification: Notification, message: NotificationMessage): AlertPagerDutyPayload {
|
||||
private buildPagerDutyPayload(notification: Notification, message: NotificationMessage): any {
|
||||
// Map our notification type to PagerDuty event_action
|
||||
const eventAction = message.type === "monitor_up" || message.type === "threshold_resolved" ? "resolve" : "trigger";
|
||||
|
||||
@@ -107,7 +106,7 @@ export class PagerDutyProvider implements INotificationProvider {
|
||||
}
|
||||
|
||||
// Build custom details
|
||||
const customDetails: Record<string, unknown> = {
|
||||
const customDetails: any = {
|
||||
monitor_name: message.monitor.name,
|
||||
monitor_url: message.monitor.url,
|
||||
monitor_type: message.monitor.type,
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
const SERVICE_NAME = "SlackProvider";
|
||||
import type { Notification } from "@/types/index.js";
|
||||
import type { Monitor, Notification, MonitorStatusResponse } from "@/types/index.js";
|
||||
import { INotificationProvider } from "@/service/index.js";
|
||||
import type { MonitorActionDecision } from "@/service/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js";
|
||||
import type { NotificationMessage } from "@/types/notificationMessage.js";
|
||||
import { getTestMessage } from "@/service/infrastructure/notificationProviders/utils.js";
|
||||
import got, { HTTPError } from "got";
|
||||
import { ILogger } from "@/utils/logger.js";
|
||||
|
||||
export class SlackProvider implements INotificationProvider {
|
||||
private logger: ILogger;
|
||||
private logger: any;
|
||||
|
||||
constructor(logger: ILogger) {
|
||||
constructor(logger: any) {
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
@@ -78,7 +78,7 @@ export class SlackProvider implements INotificationProvider {
|
||||
* Uses Slack's rich formatting with sections, fields, and context blocks
|
||||
*/
|
||||
private buildSlackPayload(message: NotificationMessage): object {
|
||||
const blocks: unknown[] = [];
|
||||
const blocks: any[] = [];
|
||||
|
||||
// Determine color based on severity
|
||||
const colorMap = {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { IEmailService } from "@/service/infrastructure/emailService.js";
|
||||
export const buildTestEmail = async (emailService: IEmailService) => {
|
||||
import { Monitor, HardwareStatusPayload, MonitorStatusResponse } from "@/types/index.js";
|
||||
// Test notification helpers - used by all providers for test alerts
|
||||
export const buildTestEmail = async (emailService: any) => {
|
||||
const context = { testName: "Monitoring System" };
|
||||
const html = await emailService.buildEmail("testEmailTemplate", context);
|
||||
return html;
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
const SERVICE_NAME = "WebhookProvider";
|
||||
import type { Notification } from "@/types/index.js";
|
||||
import type { Monitor, Alert, Notification, MonitorStatusResponse } from "@/types/index.js";
|
||||
import { INotificationProvider } from "@/service/index.js";
|
||||
import type { MonitorActionDecision } from "@/service/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js";
|
||||
import type { NotificationMessage } from "@/types/notificationMessage.js";
|
||||
import { getTestMessage } from "@/service/infrastructure/notificationProviders/utils.js";
|
||||
import got from "got";
|
||||
import { ILogger } from "@/utils/logger.js";
|
||||
|
||||
export class WebhookProvider implements INotificationProvider {
|
||||
private logger: ILogger;
|
||||
private logger: any;
|
||||
|
||||
constructor(logger: ILogger) {
|
||||
constructor(logger: any) {
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
@@ -46,6 +46,10 @@ export class WebhookProvider implements INotificationProvider {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Build webhook payload from NotificationMessage
|
||||
* Format: { text: string, severity: string, monitor: object, details: object }
|
||||
*/
|
||||
private buildWebhookPayload(message: NotificationMessage): object {
|
||||
const lines: string[] = [];
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { Monitor, MonitorStatusResponse, Notification } from "@/types/index.js";
|
||||
import type { HardwareStatusPayload, Monitor, MonitorStatusResponse, Notification, MonitorStatus } from "@/types/index.js";
|
||||
import type { NotificationMessage } from "@/types/notificationMessage.js";
|
||||
import { IMonitorsRepository, INotificationsRepository } from "@/repositories/index.js";
|
||||
import { INotificationProvider } from "./notificationProviders/INotificationProvider.js";
|
||||
|
||||
@@ -15,15 +15,13 @@ import type {
|
||||
GrpcStatusPayload,
|
||||
CheckSnapshot,
|
||||
MonitorStats,
|
||||
CheckDiskInfo,
|
||||
} from "@/types/index.js";
|
||||
import { AppError } from "@/utils/AppError.js";
|
||||
import { ILogger } from "@/utils/logger.js";
|
||||
import { IBufferService } from "./bufferService.js";
|
||||
const SERVICE_NAME = "StatusService";
|
||||
|
||||
export interface IStatusService {
|
||||
updateRunningStats(monitor: Monitor, networkResponse: MonitorStatusResponse): Promise<boolean>;
|
||||
updateRunningStats({ monitor, networkResponse }: { monitor: Monitor; networkResponse: any }): Promise<boolean>;
|
||||
handleIncidentForCheck(check: any, monitor: Monitor, action: any, errorContext?: string): Promise<void>;
|
||||
updateMonitorStatus(
|
||||
statusResponse: MonitorStatusResponse<
|
||||
| PingStatusPayload
|
||||
@@ -42,15 +40,15 @@ export interface IStatusService {
|
||||
|
||||
export class StatusService implements IStatusService {
|
||||
static SERVICE_NAME = SERVICE_NAME;
|
||||
private logger: ILogger;
|
||||
private buffer: IBufferService;
|
||||
private logger: any;
|
||||
private buffer: any;
|
||||
private monitorsRepository: IMonitorsRepository;
|
||||
private monitorStatsRepository: IMonitorStatsRepository;
|
||||
private checksRepository: IChecksRepository;
|
||||
|
||||
constructor(
|
||||
logger: ILogger,
|
||||
buffer: IBufferService,
|
||||
buffer: any,
|
||||
monitorsRepository: IMonitorsRepository,
|
||||
monitorStatsRepository: IMonitorStatsRepository,
|
||||
checksRepository: IChecksRepository
|
||||
@@ -66,7 +64,7 @@ export class StatusService implements IStatusService {
|
||||
return StatusService.SERVICE_NAME;
|
||||
}
|
||||
|
||||
async updateRunningStats(monitor: Monitor, networkResponse: MonitorStatusResponse) {
|
||||
async updateRunningStats({ monitor, networkResponse }: { monitor: Monitor; networkResponse: MonitorStatusResponse }) {
|
||||
try {
|
||||
const monitorId = monitor.id;
|
||||
const { responseTime, status } = networkResponse;
|
||||
@@ -166,17 +164,62 @@ export class StatusService implements IStatusService {
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (error: unknown) {
|
||||
} catch (error: any) {
|
||||
this.logger.error({
|
||||
service: SERVICE_NAME,
|
||||
message: error instanceof Error ? error.message : "Unknown error",
|
||||
message: error.message,
|
||||
method: "updateRunningStats",
|
||||
stack: error instanceof Error ? error.stack : undefined,
|
||||
stack: error.stack,
|
||||
});
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
handleIncidentForCheck = async (check: Check, monitor: Monitor, action: any, errorContext = "incident handling") => {
|
||||
try {
|
||||
let savedCheck: Check | null = null;
|
||||
if (!check.id) {
|
||||
try {
|
||||
savedCheck = await this.checksRepository.create(check);
|
||||
|
||||
this.buffer.removeCheckFromBuffer(check);
|
||||
} catch (checkError: any) {
|
||||
this.logger.error({
|
||||
service: SERVICE_NAME,
|
||||
method: "handleIncidentForCheck",
|
||||
message: `Failed to save check immediately for ${errorContext}: ${checkError.message}`,
|
||||
monitorId: monitor.id,
|
||||
stack: checkError.stack,
|
||||
});
|
||||
savedCheck = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (savedCheck && savedCheck.id) {
|
||||
try {
|
||||
this.buffer.addIncidentToBuffer({ monitor, check: savedCheck, action });
|
||||
} catch (incidentError: any) {
|
||||
this.logger.error({
|
||||
service: SERVICE_NAME,
|
||||
method: "handleIncidentForCheck",
|
||||
message: `Failed to add incident to buffer for ${errorContext}: ${incidentError.message}`,
|
||||
monitorId: monitor.id,
|
||||
action,
|
||||
stack: incidentError.stack,
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (error: any) {
|
||||
this.logger.error({
|
||||
service: SERVICE_NAME,
|
||||
method: "handleIncidentForCheck",
|
||||
message: `Error in ${errorContext}: ${error.message}`,
|
||||
monitorId: monitor?.id,
|
||||
stack: error.stack,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
updateMonitorStatus = async (
|
||||
statusResponse: MonitorStatusResponse<
|
||||
| PingStatusPayload
|
||||
@@ -196,7 +239,7 @@ export class StatusService implements IStatusService {
|
||||
const monitor = await this.monitorsRepository.findById(monitorId, teamId);
|
||||
|
||||
// Update running stats
|
||||
this.updateRunningStats(monitor, statusResponse);
|
||||
this.updateRunningStats({ monitor, networkResponse: statusResponse });
|
||||
|
||||
monitor.statusWindow = monitor.statusWindow || [];
|
||||
monitor.statusWindow.push(status);
|
||||
@@ -279,8 +322,7 @@ export class StatusService implements IStatusService {
|
||||
const memoryBreach = memoryUsage !== -1 && memoryUsage > monitor.memoryAlertThreshold / 100;
|
||||
|
||||
const diskBreach =
|
||||
metrics.disk?.some((d: CheckDiskInfo) => typeof d?.usage_percent === "number" && d.usage_percent > monitor.diskAlertThreshold / 100) ??
|
||||
false;
|
||||
metrics.disk?.some((d: any) => typeof d?.usage_percent === "number" && d.usage_percent > monitor.diskAlertThreshold / 100) ?? false;
|
||||
|
||||
const temps = metrics.cpu?.temperature ?? [];
|
||||
const tempBreach = temps.some((temp: number) => temp > monitor.tempAlertThreshold);
|
||||
@@ -358,13 +400,10 @@ export class StatusService implements IStatusService {
|
||||
timestamp: new Date().getTime(),
|
||||
thresholdBreaches,
|
||||
};
|
||||
} catch (error: unknown) {
|
||||
throw new AppError({
|
||||
message: `Failed to update monitor status: ${error instanceof Error ? error.message : "Unknown error"}`,
|
||||
details: `Error updating status for monitor with id ${check.metadata.monitorId}`,
|
||||
service: SERVICE_NAME,
|
||||
method: "updateMonitorStatus",
|
||||
});
|
||||
} catch (error: any) {
|
||||
error.service = SERVICE_NAME;
|
||||
error.method = "updateStatus";
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -380,13 +419,13 @@ export class StatusService implements IStatusService {
|
||||
}
|
||||
this.buffer.addToBuffer(check);
|
||||
return true;
|
||||
} catch (error: unknown) {
|
||||
} catch (error: any) {
|
||||
this.logger.error({
|
||||
message: error instanceof Error ? error.message : "Unknown error",
|
||||
service: SERVICE_NAME,
|
||||
method: "insertCheck",
|
||||
details: { msg: `Error inserting check for monitor: ${check?.metadata.monitorId}` },
|
||||
stack: error instanceof Error ? error.stack : undefined,
|
||||
message: error.message,
|
||||
service: error.service || SERVICE_NAME,
|
||||
method: error.method || "insertCheck",
|
||||
details: error.details || `Error inserting check for monitor: ${check?.metadata.monitorId}`,
|
||||
stack: error.stack,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -22,7 +22,7 @@ export interface ISettingsService {
|
||||
}
|
||||
|
||||
class SettingsService implements ISettingsService {
|
||||
static SERVICE_NAME = SERVICE_NAME;
|
||||
static SERVICE_NAME = "SettingsService";
|
||||
private settings: EnvConfig;
|
||||
private settingsRepository: ISettingsRepository;
|
||||
|
||||
|
||||
@@ -4,29 +4,16 @@ export interface AlertWebhookPayload {
|
||||
}
|
||||
|
||||
export interface AlertPagerDutyPayload {
|
||||
routing_key?: string;
|
||||
dedup_key?: string;
|
||||
event_action?: "trigger" | "resolve";
|
||||
payload: Record<string, unknown>;
|
||||
routingKey: string;
|
||||
monitorUrl?: string;
|
||||
}
|
||||
|
||||
export interface AlertMatrixPayload {
|
||||
plainText: string;
|
||||
htmlText: string;
|
||||
}
|
||||
|
||||
export interface DiscordEmbedField {
|
||||
name: string;
|
||||
value: string;
|
||||
inline?: boolean;
|
||||
}
|
||||
|
||||
export interface AlertDiscordPayload {
|
||||
title: string;
|
||||
description: string;
|
||||
color: number;
|
||||
fields: DiscordEmbedField[];
|
||||
timestamp: string;
|
||||
friendlyName?: string;
|
||||
homeserverUrl: string;
|
||||
accessToken: string;
|
||||
roomId: string;
|
||||
monitorName: string;
|
||||
}
|
||||
|
||||
export interface Alert {
|
||||
|
||||
Vendored
+1
-1
@@ -5,7 +5,7 @@ declare global {
|
||||
interface Request {
|
||||
file?: Multer.File;
|
||||
user?: User | undefined;
|
||||
resource?: unknown;
|
||||
resource?: any;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@ export type DurationUnit = "seconds" | "minutes" | "hours" | "days";
|
||||
|
||||
export interface MaintenanceWindow {
|
||||
id: string;
|
||||
monitorId: string;
|
||||
monitorId: String;
|
||||
teamId: string;
|
||||
active: boolean;
|
||||
name: string;
|
||||
|
||||
@@ -3,7 +3,7 @@ export type { CheckSnapshot } from "@/types/check.js";
|
||||
import type { GeoContinent } from "@/types/geoCheck.js";
|
||||
export type { GeoContinent } from "@/types/geoCheck.js";
|
||||
|
||||
export const MonitorTypes = ["http", "ping", "pagespeed", "hardware", "docker", "port", "game", "grpc", "unknown"] as const;
|
||||
export const MonitorTypes = ["http", "ping", "pagespeed", "hardware", "docker", "port", "game", "grpc", "websocket", "unknown"] as const;
|
||||
export type MonitorType = (typeof MonitorTypes)[number];
|
||||
|
||||
export const MonitorStatuses = ["up", "down", "paused", "initializing", "maintenance", "breached"] as const;
|
||||
@@ -27,8 +27,6 @@ export interface Monitor {
|
||||
jsonPath?: string;
|
||||
expectedValue?: string;
|
||||
matchMethod?: MonitorMatchMethod;
|
||||
basicAuthUser?: string;
|
||||
basicAuthPassword?: string;
|
||||
url: string;
|
||||
port?: number;
|
||||
isActive: boolean;
|
||||
|
||||
@@ -109,6 +109,10 @@ export interface GrpcStatusPayload {
|
||||
servingStatus: string;
|
||||
}
|
||||
|
||||
export interface WebSocketStatusPayload {
|
||||
connected: boolean;
|
||||
}
|
||||
|
||||
export interface MonitorPayloadMap {
|
||||
ping: PingStatusPayload;
|
||||
http: HttpStatusPayload;
|
||||
@@ -118,6 +122,7 @@ export interface MonitorPayloadMap {
|
||||
port: PortStatusPayload;
|
||||
game: GameStatusPayload;
|
||||
grpc: GrpcStatusPayload;
|
||||
websocket: WebSocketStatusPayload;
|
||||
default: unknown;
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ export const getChecksParamValidation = z.object({
|
||||
});
|
||||
|
||||
export const getChecksQueryValidation = z.object({
|
||||
type: z.enum(["http", "ping", "pagespeed", "hardware", "docker", "port", "game", "grpc"]).optional(),
|
||||
type: z.enum(["http", "ping", "pagespeed", "hardware", "docker", "port", "game", "grpc", "websocket"]).optional(),
|
||||
sortOrder: z.enum(["asc", "desc"]).optional(),
|
||||
limit: z.coerce.number().optional(),
|
||||
dateRange: z.enum(["recent", "hour", "day", "week", "month", "all"]).optional(),
|
||||
|
||||
@@ -21,8 +21,8 @@ export const getMonitorsByTeamIdParamValidation = z.object({});
|
||||
export const getMonitorsByTeamIdQueryValidation = z.object({
|
||||
type: z
|
||||
.union([
|
||||
z.enum(["http", "ping", "pagespeed", "docker", "hardware", "port", "game", "grpc"]),
|
||||
z.array(z.enum(["http", "ping", "pagespeed", "docker", "hardware", "port", "game", "grpc"])),
|
||||
z.enum(["http", "ping", "pagespeed", "docker", "hardware", "port", "game", "grpc", "websocket"]),
|
||||
z.array(z.enum(["http", "ping", "pagespeed", "docker", "hardware", "port", "game", "grpc", "websocket"])),
|
||||
])
|
||||
.optional(),
|
||||
filter: z.union([z.string(), z.literal(""), z.null()]).optional(),
|
||||
@@ -37,8 +37,8 @@ export const getMonitorsWithChecksQueryValidation = z.object({
|
||||
order: z.enum(["asc", "desc"]).optional(),
|
||||
type: z
|
||||
.union([
|
||||
z.enum(["http", "ping", "pagespeed", "docker", "hardware", "port", "game", "grpc"]),
|
||||
z.array(z.enum(["http", "ping", "pagespeed", "docker", "hardware", "port", "game", "grpc"])),
|
||||
z.enum(["http", "ping", "pagespeed", "docker", "hardware", "port", "game", "grpc", "websocket"]),
|
||||
z.array(z.enum(["http", "ping", "pagespeed", "docker", "hardware", "port", "game", "grpc", "websocket"])),
|
||||
])
|
||||
.optional(),
|
||||
explain: booleanCoercion.optional(),
|
||||
@@ -70,8 +70,6 @@ export const createMonitorBodyValidation = z.object({
|
||||
jsonPath: 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(),
|
||||
basicAuthUser: z.union([z.string(), z.literal("")]).optional(),
|
||||
basicAuthPassword: z.union([z.string(), z.literal("")]).optional(),
|
||||
gameId: z.union([z.string(), z.literal("")]).optional(),
|
||||
grpcServiceName: z.union([z.string(), z.literal("")]).default(""),
|
||||
selectedDisks: z.array(z.string()).optional(),
|
||||
@@ -96,8 +94,6 @@ export const editMonitorBodyValidation = z.object({
|
||||
jsonPath: 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(),
|
||||
basicAuthUser: z.union([z.string(), z.literal("")]).optional(),
|
||||
basicAuthPassword: z.union([z.string(), z.literal("")]).optional(),
|
||||
port: z.number().min(1).max(65535).optional(),
|
||||
cpuAlertThreshold: z.number().optional(),
|
||||
memoryAlertThreshold: z.number().optional(),
|
||||
|
||||
Reference in new issue
Block a user