Compare commits

...
18 changed files with 183 additions and 82 deletions

No files matched your search

+25 -67
View File
@@ -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"];
+8
View File
@@ -83,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,
+21 -1
View File
@@ -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")}
+1 -1
View File
@@ -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(() => {
+1
View File
@@ -12,6 +12,7 @@ export const MonitorTypes = [
"port",
"game",
"grpc",
"websocket",
"unknown",
] as const;
export type MonitorType = (typeof MonitorTypes)[number];
+1
View File
@@ -9,6 +9,7 @@ export const getMonitorPath = (type: MonitorType): string => {
ping: "uptime",
game: "uptime",
grpc: "uptime",
websocket: "uptime",
unknown: "uptime",
docker: "uptime",
hardware: "infrastructure",
+9
View File
@@ -85,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"),
@@ -123,6 +130,7 @@ export const monitorSchema = z.discriminatedUnion("type", [
dockerSchema,
gameSchema,
grpcSchema,
websocketSchema,
pagespeedSchema,
hardwareSchema,
]);
@@ -137,6 +145,7 @@ export {
dockerSchema,
gameSchema,
grpcSchema,
websocketSchema,
pagespeedSchema,
hardwareSchema,
};
+7
View File
@@ -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,6 +502,7 @@
"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."
}
},
@@ -534,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",
+16 -3
View File
@@ -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"
+2
View File
@@ -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 -2
View File
@@ -29,7 +29,6 @@ import { NotificationMessageBuilder, INotificationMessageBuilder } from "../serv
import axios from "axios";
import got from "got";
import ping from "ping";
import http from "http";
import https from "https";
import Docker from "dockerode";
import net from "net";
@@ -45,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 {
@@ -159,7 +159,8 @@ export const initializeServices = async ({
net,
settingsService,
grpc,
protoLoader
protoLoader,
WebSocket
);
const emailService = new EmailService(settingsService, fs, path, compile, mjml2html, nodemailer, logger);
@@ -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 });
}
@@ -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);
}
@@ -853,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 -1
View File
@@ -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;
+5
View File
@@ -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;
}
+1 -1
View File
@@ -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(),
+4 -4
View File
@@ -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(),