mirror of
https://github.com/bluewave-labs/Checkmate.git
synced 2026-05-19 16:08:39 -05:00
Merge branch 'develop' into refactor-login-page
This commit is contained in:
@@ -50,6 +50,7 @@ const Select = ({
|
||||
onChange,
|
||||
onBlur,
|
||||
sx,
|
||||
error = false,
|
||||
name = "",
|
||||
labelControlSpacing = 6,
|
||||
maxWidth,
|
||||
@@ -93,6 +94,7 @@ const Select = ({
|
||||
onChange={onChange}
|
||||
onBlur={onBlur}
|
||||
displayEmpty
|
||||
error={error}
|
||||
name={name}
|
||||
inputProps={{ id: id }}
|
||||
IconComponent={KeyboardArrowDownIcon}
|
||||
@@ -172,6 +174,7 @@ Select.propTypes = {
|
||||
label: PropTypes.string,
|
||||
placeholder: PropTypes.string,
|
||||
isHidden: PropTypes.bool,
|
||||
error: PropTypes.bool,
|
||||
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.bool])
|
||||
.isRequired,
|
||||
items: PropTypes.arrayOf(
|
||||
|
||||
@@ -202,6 +202,25 @@ const useFetchStatsByMonitorId = ({
|
||||
return [monitor, audits, isLoading, networkError];
|
||||
};
|
||||
|
||||
const useFetchMonitorGames = ({ setGames, updateTrigger }) => {
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
useEffect(() => {
|
||||
const fetchGames = async () => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
const res = await networkService.getMonitorGames();
|
||||
setGames(res.data.data);
|
||||
} catch (error) {
|
||||
createToast({ body: error.message });
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
fetchGames();
|
||||
}, [setGames, updateTrigger]);
|
||||
return [isLoading];
|
||||
};
|
||||
|
||||
const useFetchMonitorById = ({ monitorId, setMonitor, updateTrigger }) => {
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
useEffect(() => {
|
||||
@@ -357,7 +376,12 @@ const useUpdateMonitor = () => {
|
||||
expectedValue: monitor.expectedValue,
|
||||
ignoreTlsErrors: monitor.ignoreTlsErrors,
|
||||
jsonPath: monitor.jsonPath,
|
||||
...(monitor.type === "port" && { port: monitor.port }),
|
||||
...((monitor.type === "port" || monitor.type === "game") && {
|
||||
port: monitor.port,
|
||||
}),
|
||||
...(monitor.type == "game" && {
|
||||
gameId: monitor.gameId,
|
||||
}),
|
||||
...(monitor.type === "hardware" && {
|
||||
thresholds: monitor.thresholds,
|
||||
secret: monitor.secret,
|
||||
@@ -530,4 +554,5 @@ export {
|
||||
useDeleteMonitorStats,
|
||||
useCreateBulkMonitors,
|
||||
useExportMonitors,
|
||||
useFetchMonitorGames,
|
||||
};
|
||||
|
||||
@@ -41,6 +41,7 @@ import {
|
||||
useUpdateMonitor,
|
||||
usePauseMonitor,
|
||||
useFetchMonitorById,
|
||||
useFetchMonitorGames,
|
||||
} from "../../../Hooks/monitorHooks";
|
||||
|
||||
/**
|
||||
@@ -81,6 +82,7 @@ const UptimeCreate = ({ isClone = false }) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [useAdvancedMatching, setUseAdvancedMatching] = useState(false);
|
||||
const [updateTrigger, setUpdateTrigger] = useState(false);
|
||||
const [games, setGames] = useState({});
|
||||
const triggerUpdate = () => {
|
||||
setUpdateTrigger(!updateTrigger);
|
||||
};
|
||||
@@ -89,12 +91,22 @@ const UptimeCreate = ({ isClone = false }) => {
|
||||
const [notifications, notificationsAreLoading, notificationsError] =
|
||||
useGetNotificationsByTeamId();
|
||||
const { determineState, statusColor } = useMonitorUtils();
|
||||
// Network
|
||||
const [isLoading] = useFetchMonitorById({
|
||||
// Fetch monitor details
|
||||
const [isFetchingMonitor] = useFetchMonitorById({
|
||||
monitorId,
|
||||
setMonitor,
|
||||
updateTrigger: true,
|
||||
});
|
||||
|
||||
// Fetch games
|
||||
const [isFetchingGames] = useFetchMonitorGames({
|
||||
setGames,
|
||||
triggerUpdate: true,
|
||||
});
|
||||
|
||||
// Combine the loading states
|
||||
const isLoading = isFetchingMonitor || isFetchingGames;
|
||||
|
||||
const [createMonitor, isCreating] = useCreateMonitor();
|
||||
const [pauseMonitor, isPausing] = usePauseMonitor({});
|
||||
const [deleteMonitor, isDeleting] = useDeleteMonitor();
|
||||
@@ -113,6 +125,12 @@ const UptimeCreate = ({ isClone = false }) => {
|
||||
{ _id: 4, name: t("time.fourMinutes") },
|
||||
{ _id: 5, name: t("time.fiveMinutes") },
|
||||
];
|
||||
|
||||
const GAMELIST = Object.entries(games).map(([key, value]) => ({
|
||||
_id: key,
|
||||
name: value.name,
|
||||
}));
|
||||
|
||||
const CRUMBS = [
|
||||
{ name: "uptime", path: "/uptime" },
|
||||
...(isCreate
|
||||
@@ -153,6 +171,11 @@ const UptimeCreate = ({ isClone = false }) => {
|
||||
placeholder: t("monitorType.port.placeholder"),
|
||||
namePlaceholder: t("monitorType.port.namePlaceholder"),
|
||||
},
|
||||
game: {
|
||||
label: t("monitorType.game.label"),
|
||||
placeholder: t("monitorType.game.placeholder"),
|
||||
namePlaceholder: t("monitorType.game.namePlaceholder"),
|
||||
},
|
||||
};
|
||||
|
||||
// Handlers
|
||||
@@ -169,12 +192,14 @@ const UptimeCreate = ({ isClone = false }) => {
|
||||
: monitor.url,
|
||||
name: monitor.name || monitor.url.substring(0, 50),
|
||||
type: monitor.type,
|
||||
port: monitor.type === "port" ? monitor.port : undefined,
|
||||
port:
|
||||
monitor.type === "port" || monitor.type === "game" ? monitor.port : undefined,
|
||||
interval: monitor.interval,
|
||||
matchMethod: monitor.matchMethod,
|
||||
expectedValue: monitor.expectedValue,
|
||||
jsonPath: monitor.jsonPath,
|
||||
ignoreTlsErrors: monitor.ignoreTlsErrors,
|
||||
gameId: monitor.gameId || undefined,
|
||||
};
|
||||
} else {
|
||||
form = {
|
||||
@@ -188,8 +213,10 @@ const UptimeCreate = ({ isClone = false }) => {
|
||||
interval: monitor.interval,
|
||||
teamId: monitor.teamId,
|
||||
userId: monitor.userId,
|
||||
port: monitor.type === "port" ? monitor.port : undefined,
|
||||
port:
|
||||
monitor.type === "port" || monitor.type === "game" ? monitor.port : undefined,
|
||||
ignoreTlsErrors: monitor.ignoreTlsErrors,
|
||||
gameId: monitor.gameId || undefined,
|
||||
};
|
||||
}
|
||||
if (!useAdvancedMatching) {
|
||||
@@ -243,6 +270,11 @@ const UptimeCreate = ({ isClone = false }) => {
|
||||
|
||||
setMonitor((prev) => ({ ...prev, [name]: value }));
|
||||
|
||||
if (name === "type") {
|
||||
setErrors({});
|
||||
return;
|
||||
}
|
||||
|
||||
const { error } = monitorValidation.validate(
|
||||
{ type: monitor.type, [name]: value },
|
||||
{ abortEarly: false }
|
||||
@@ -250,7 +282,9 @@ const UptimeCreate = ({ isClone = false }) => {
|
||||
|
||||
setErrors((prev) => ({
|
||||
...prev,
|
||||
...(error ? { [name]: error.details[0].message } : { [name]: undefined }),
|
||||
...(error && error.details[0].path[0] === name
|
||||
? { [name]: error.details[0].message }
|
||||
: { [name]: undefined }),
|
||||
}));
|
||||
};
|
||||
|
||||
@@ -486,6 +520,15 @@ const UptimeCreate = ({ isClone = false }) => {
|
||||
checked={monitor.type === "port"}
|
||||
onChange={onChange}
|
||||
/>
|
||||
<Radio
|
||||
name="type"
|
||||
title={t("gameServerMonitoring")}
|
||||
desc={t("gameServerMonitoringDescription")}
|
||||
size="small"
|
||||
value="game"
|
||||
checked={monitor.type === "game"}
|
||||
onChange={onChange}
|
||||
/>
|
||||
{errors["type"] ? (
|
||||
<Box className="error-container">
|
||||
<Typography
|
||||
@@ -547,8 +590,19 @@ const UptimeCreate = ({ isClone = false }) => {
|
||||
onChange={onChange}
|
||||
error={errors["port"] ? true : false}
|
||||
helperText={errors["port"]}
|
||||
hidden={monitor.type !== "port"}
|
||||
hidden={monitor.type !== "port" && monitor.type !== "game"}
|
||||
/>
|
||||
{monitor.type === "game" && (
|
||||
<Select
|
||||
name="gameId"
|
||||
label={t("chooseGame")}
|
||||
value={monitor.gameId || ""}
|
||||
placeholder={t("chooseGame")}
|
||||
onChange={onChange}
|
||||
items={GAMELIST}
|
||||
error={errors["gameId"] ? true : false}
|
||||
/>
|
||||
)}
|
||||
<TextInput
|
||||
name="name"
|
||||
type="text"
|
||||
|
||||
@@ -32,6 +32,7 @@ const getTypeOptions = () => [
|
||||
{ value: "ping", label: "Ping" },
|
||||
{ value: "docker", label: "Docker" },
|
||||
{ value: "port", label: "Port" },
|
||||
{ value: "game", label: "Game" },
|
||||
];
|
||||
|
||||
// These functions were moved inline to ensure translations are applied correctly
|
||||
|
||||
@@ -34,7 +34,7 @@ import {
|
||||
} from "../../../Hooks/monitorHooks";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const TYPES = ["http", "ping", "docker", "port"];
|
||||
const TYPES = ["http", "ping", "docker", "port", "game"];
|
||||
const CreateMonitorButton = ({ shouldRender }) => {
|
||||
// Utils
|
||||
const navigate = useNavigate();
|
||||
|
||||
@@ -74,6 +74,20 @@ class NetworkService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the games associated with a monitor
|
||||
*
|
||||
* @async
|
||||
* @returns {Promise<AxiosResponse>} The response from the axios GET request.
|
||||
*/
|
||||
async getMonitorGames() {
|
||||
return this.axiosInstance.get(`/monitors/games`, {
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* ************************************
|
||||
|
||||
@@ -184,12 +184,12 @@ const monitorValidation = joi.object({
|
||||
.min(1)
|
||||
.max(65535)
|
||||
.when("type", {
|
||||
is: "port",
|
||||
then: joi.number().messages({
|
||||
is: joi.valid("port", "game"),
|
||||
then: joi.required().messages({
|
||||
"number.base": "Port must be a number.",
|
||||
"number.min": "Port must be at least 1.",
|
||||
"number.max": "Port must be at most 65535.",
|
||||
"any.required": "Port is required for port monitors.",
|
||||
"any.required": "Port is required for port and game monitors.",
|
||||
}),
|
||||
otherwise: joi.optional(),
|
||||
}),
|
||||
@@ -205,6 +205,14 @@ const monitorValidation = joi.object({
|
||||
expectedValue: joi.string().allow(null, ""),
|
||||
jsonPath: joi.string().allow(null, ""),
|
||||
matchMethod: joi.string().allow(null, ""),
|
||||
gameId: joi.when("type", {
|
||||
is: "game",
|
||||
then: joi.string().required().messages({
|
||||
"string.empty": "Game selection is required for game monitors.",
|
||||
"any.required": "Game selection is required for game monitors.",
|
||||
}),
|
||||
otherwise: joi.string().allow(null, ""),
|
||||
}),
|
||||
});
|
||||
|
||||
const imageValidation = joi.object({
|
||||
|
||||
@@ -260,6 +260,7 @@
|
||||
"notifyEmails": "",
|
||||
"seperateEmails": "",
|
||||
"checkFrequency": "",
|
||||
"chooseGame": "",
|
||||
"matchMethod": "",
|
||||
"expectedValue": "",
|
||||
"deleteDialogTitle": "",
|
||||
@@ -437,7 +438,8 @@
|
||||
"http": "",
|
||||
"ping": "",
|
||||
"docker": "",
|
||||
"port": ""
|
||||
"port": "",
|
||||
"game": ""
|
||||
},
|
||||
"common": {
|
||||
"appName": "",
|
||||
|
||||
@@ -260,6 +260,7 @@
|
||||
"notifyEmails": "",
|
||||
"seperateEmails": "",
|
||||
"checkFrequency": "",
|
||||
"chooseGame": "",
|
||||
"matchMethod": "",
|
||||
"expectedValue": "",
|
||||
"deleteDialogTitle": "",
|
||||
@@ -437,7 +438,8 @@
|
||||
"http": "",
|
||||
"ping": "",
|
||||
"docker": "",
|
||||
"port": ""
|
||||
"port": "",
|
||||
"game": ""
|
||||
},
|
||||
"common": {
|
||||
"appName": "Checkmate",
|
||||
|
||||
@@ -260,6 +260,7 @@
|
||||
"notifyEmails": "Benachrichtige auch per E-Mail an mehrere Adressen (kommt bald)",
|
||||
"seperateEmails": "Du kannst mehrere E-Mails mit einem Komma trennen",
|
||||
"checkFrequency": "Überprüfungsfrequenz",
|
||||
"chooseGame": "",
|
||||
"matchMethod": "",
|
||||
"expectedValue": "Erwarteter Wert",
|
||||
"deleteDialogTitle": "Möchtest du diesen Monitor wirklich löschen?",
|
||||
@@ -437,7 +438,8 @@
|
||||
"http": "",
|
||||
"ping": "",
|
||||
"docker": "",
|
||||
"port": ""
|
||||
"port": "",
|
||||
"game": ""
|
||||
},
|
||||
"common": {
|
||||
"appName": "Checkmate",
|
||||
|
||||
@@ -218,6 +218,7 @@
|
||||
"cancel": "Cancel",
|
||||
"checkFormError": "Please check the form for errors.",
|
||||
"checkFrequency": "Check frequency",
|
||||
"chooseGame": "Choose game",
|
||||
"checkHooks": {
|
||||
"failureResolveAll": "Failed to resolve all incidents.",
|
||||
"failureResolveMonitor": "Failed to resolve monitor incidents.",
|
||||
@@ -629,6 +630,11 @@
|
||||
"label": "URL to monitor",
|
||||
"namePlaceholder": "Localhost:5173",
|
||||
"placeholder": "localhost"
|
||||
},
|
||||
"game": {
|
||||
"label": "URL to monitor",
|
||||
"namePlaceholder": "localhost:5173",
|
||||
"placeholder": "localhost"
|
||||
}
|
||||
},
|
||||
"ms": "ms",
|
||||
@@ -750,6 +756,8 @@
|
||||
"pingMonitoringDescription": "Check whether your server is available or not.",
|
||||
"portMonitoring": "Port monitoring",
|
||||
"portMonitoringDescription": "Check whether your port is open or not.",
|
||||
"gameServerMonitoring": "Game Server Monitoring",
|
||||
"gameServerMonitoringDescription": "Check whether your game server is running or not",
|
||||
"portToMonitor": "Port to monitor",
|
||||
"publicLink": "Public link",
|
||||
"publicURL": "Public URL",
|
||||
@@ -1012,7 +1020,8 @@
|
||||
"docker": "Enter the Docker ID of your container. Docker IDs must be the full 64 char Docker ID. You can run docker inspect <short_id> to get the full container ID.",
|
||||
"http": "Enter the URL or IP to monitor (e.g., https://example.com/ or 192.168.1.100) and add a clear display name that appears on the dashboard.",
|
||||
"ping": "Enter the IP address or hostname to ping (e.g., 192.168.1.100 or example.com) and add a clear display name that appears on the dashboard.",
|
||||
"port": "Enter the URL or IP of the server, the port number and a clear display name that appears on the dashboard."
|
||||
"port": "Enter the URL or IP of the server, the port number and a clear display name that appears on the dashboard.",
|
||||
"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."
|
||||
},
|
||||
"uptimeMonitor": {
|
||||
"fallback": {
|
||||
|
||||
@@ -260,6 +260,7 @@
|
||||
"notifyEmails": "",
|
||||
"seperateEmails": "",
|
||||
"checkFrequency": "",
|
||||
"chooseGame": "",
|
||||
"matchMethod": "",
|
||||
"expectedValue": "",
|
||||
"deleteDialogTitle": "",
|
||||
@@ -437,7 +438,8 @@
|
||||
"http": "",
|
||||
"ping": "",
|
||||
"docker": "",
|
||||
"port": ""
|
||||
"port": "",
|
||||
"game": ""
|
||||
},
|
||||
"common": {
|
||||
"appName": "",
|
||||
|
||||
@@ -260,6 +260,7 @@
|
||||
"notifyEmails": "",
|
||||
"seperateEmails": "",
|
||||
"checkFrequency": "",
|
||||
"chooseGame": "",
|
||||
"matchMethod": "",
|
||||
"expectedValue": "",
|
||||
"deleteDialogTitle": "",
|
||||
@@ -437,7 +438,8 @@
|
||||
"http": "",
|
||||
"ping": "",
|
||||
"docker": "",
|
||||
"port": ""
|
||||
"port": "",
|
||||
"game": ""
|
||||
},
|
||||
"common": {
|
||||
"appName": "",
|
||||
|
||||
@@ -264,6 +264,7 @@
|
||||
"notifyEmails": "Envoyer également une notification par email à plusieurs adresses (bientôt).",
|
||||
"seperateEmails": "Vous pouvez ajouter plusieurs adresses emails en les séparant par une virgule",
|
||||
"checkFrequency": "Vérifier la fréquence",
|
||||
"chooseGame": "",
|
||||
"matchMethod": "Méthode de rapprochement",
|
||||
"expectedValue": "Valeur attendue",
|
||||
"deleteDialogTitle": "Voulez-vous vraiment supprimer ce moniteur ?",
|
||||
@@ -441,7 +442,8 @@
|
||||
"http": "Entrez l'URL ou l'IP du moniteur (par exemple https://exemple.fr ou 192.168.1.100) et ajoutez un nom familier qui apparaîtra sur le tableau de bord.",
|
||||
"ping": "Entrez l'adresse IP ou le nom d'hôte à tester (par exemple, 192.168.1.100 ou exemple.fr) et ajoutez un nom familier qui apparaîtra sur le tableau de bord.",
|
||||
"docker": "Entrer l'ID Docker du container. Les identifiants Docker doivent être les 64 caractères de l'ID Docker. Vous pouvez utiliser la commande docker inspect <short_id> pour avoir l'ID complet.",
|
||||
"port": "Entrez l'URL ou l'adresse IP du serveur, le numéro de port et un nom d'affichage familier qui apparaîtra sur le tableau de bord."
|
||||
"port": "Entrez l'URL ou l'adresse IP du serveur, le numéro de port et un nom d'affichage familier qui apparaîtra sur le tableau de bord.",
|
||||
"game": ""
|
||||
},
|
||||
"common": {
|
||||
"appName": "Checkmate",
|
||||
|
||||
@@ -264,6 +264,7 @@
|
||||
"notifyEmails": "複数のアドレスにメールでも通知(近日公開)",
|
||||
"seperateEmails": "複数のメールはカンマで区切ることができます",
|
||||
"checkFrequency": "チェック頻度",
|
||||
"chooseGame": "",
|
||||
"matchMethod": "マッチ方法",
|
||||
"expectedValue": "期待値",
|
||||
"deleteDialogTitle": "本当にこのモニターを削除しますか?",
|
||||
@@ -441,7 +442,8 @@
|
||||
"http": "監視するURLまたはIPを入力(例: https://example.com/ または 192.168.1.100)し、ダッシュボードに表示される明確な表示名を追加。",
|
||||
"ping": "pingするIPアドレスまたはホスト名を入力(例: 192.168.1.100 または example.com)し、ダッシュボードに表示される明確な表示名を追加。",
|
||||
"docker": "コンテナのDocker IDを入力。Docker IDは64文字のフルDocker IDである必要があります。docker inspect <short_id>を実行してフルコンテナIDを取得できます。",
|
||||
"port": "サーバーのURLまたはIP、ポート番号、ダッシュボードに表示される明確な表示名を入力。"
|
||||
"port": "サーバーのURLまたはIP、ポート番号、ダッシュボードに表示される明確な表示名を入力。",
|
||||
"game": ""
|
||||
},
|
||||
"common": {
|
||||
"appName": "Checkmate",
|
||||
|
||||
@@ -260,6 +260,7 @@
|
||||
"notifyEmails": "Também notificar por e-mail para vários endereços (em breve)",
|
||||
"seperateEmails": "Você pode separar vários e-mails com uma vírgula",
|
||||
"checkFrequency": "Verifique a frequência",
|
||||
"chooseGame": "",
|
||||
"matchMethod": "Método de correspondência",
|
||||
"expectedValue": "Valor esperado",
|
||||
"deleteDialogTitle": "Você realmente deseja excluir este monitor?",
|
||||
@@ -437,7 +438,8 @@
|
||||
"http": "Insira a URL ou IP para monitorar (ex. https://exemplo.com.br/ ou 192.168.1.100) e adicione uma descrição que aparecerá na dashboard.",
|
||||
"ping": "Insira o endereço IP ou nome de domínio para ping (ex. 192.168.1.100 ou exemplo.com.br) e adicione uma descrição que aparecerá na dashboard.",
|
||||
"docker": "Insira o Docker Id do seu container. Docker Ids devem ser todos os 64 caracteres. Você pode executar docker inspect <short_id> para descobrir o Id completo.",
|
||||
"port": "Insira a URL ou o IP do servidor, aporta e uma descrição que aparecerá na dashboard."
|
||||
"port": "Insira a URL ou o IP do servidor, aporta e uma descrição que aparecerá na dashboard.",
|
||||
"game": ""
|
||||
},
|
||||
"common": {
|
||||
"appName": "Checkmate",
|
||||
|
||||
@@ -260,6 +260,7 @@
|
||||
"notifyEmails": "Также уведомлять по электронной почте на несколько адресов (скоро)",
|
||||
"seperateEmails": "Вы можете разделить несколько адресов электронной почты запятой.",
|
||||
"checkFrequency": "Проверить частоту",
|
||||
"chooseGame": "",
|
||||
"matchMethod": "Метод сопоставления",
|
||||
"expectedValue": "Ожидаемое значение",
|
||||
"deleteDialogTitle": "Вы действительно хотите удалить этот монитор?",
|
||||
@@ -437,7 +438,8 @@
|
||||
"http": "",
|
||||
"ping": "",
|
||||
"docker": "",
|
||||
"port": ""
|
||||
"port": "",
|
||||
"game": ""
|
||||
},
|
||||
"common": {
|
||||
"appName": "",
|
||||
|
||||
@@ -260,6 +260,7 @@
|
||||
"notifyEmails": "Ayrıca birden fazla eposta adresine bildirim gönderebilirsiniz (yakında geliyor)",
|
||||
"seperateEmails": "Birden fazla epostayı virgülle ayırabilirsiniz",
|
||||
"checkFrequency": "Frekansı denetle",
|
||||
"chooseGame": "Oyun seç",
|
||||
"matchMethod": "Eşleşme yöntemi",
|
||||
"expectedValue": "Beklenen değer",
|
||||
"deleteDialogTitle": "Gerçekten bu monitörü silmek istiyor musunuz?",
|
||||
@@ -437,7 +438,8 @@
|
||||
"http": "",
|
||||
"ping": "",
|
||||
"docker": "",
|
||||
"port": ""
|
||||
"port": "",
|
||||
"game": ""
|
||||
},
|
||||
"common": {
|
||||
"appName": "Checkmate",
|
||||
|
||||
@@ -260,6 +260,7 @@
|
||||
"notifyEmails": "",
|
||||
"seperateEmails": "",
|
||||
"checkFrequency": "",
|
||||
"chooseGame": "",
|
||||
"matchMethod": "",
|
||||
"expectedValue": "",
|
||||
"deleteDialogTitle": "",
|
||||
@@ -435,7 +436,8 @@
|
||||
"http": "",
|
||||
"ping": "",
|
||||
"docker": "",
|
||||
"port": ""
|
||||
"port": "",
|
||||
"game": ""
|
||||
},
|
||||
"common": {
|
||||
"appName": "",
|
||||
|
||||
@@ -260,6 +260,7 @@
|
||||
"notifyEmails": "",
|
||||
"seperateEmails": "",
|
||||
"checkFrequency": "",
|
||||
"chooseGame": "",
|
||||
"matchMethod": "",
|
||||
"expectedValue": "",
|
||||
"deleteDialogTitle": "",
|
||||
@@ -435,7 +436,8 @@
|
||||
"http": "",
|
||||
"ping": "",
|
||||
"docker": "",
|
||||
"port": ""
|
||||
"port": "",
|
||||
"game": ""
|
||||
},
|
||||
"common": {
|
||||
"appName": "",
|
||||
|
||||
@@ -260,6 +260,7 @@
|
||||
"notifyEmails": "",
|
||||
"seperateEmails": "",
|
||||
"checkFrequency": "",
|
||||
"chooseGame": "",
|
||||
"matchMethod": "",
|
||||
"expectedValue": "",
|
||||
"deleteDialogTitle": "",
|
||||
@@ -437,7 +438,8 @@
|
||||
"http": "",
|
||||
"ping": "",
|
||||
"docker": "",
|
||||
"port": ""
|
||||
"port": "",
|
||||
"game": ""
|
||||
},
|
||||
"common": {
|
||||
"appName": "",
|
||||
|
||||
Executable → Regular
+2773
-1947
File diff suppressed because it is too large
Load Diff
Generated
+881
-71
File diff suppressed because it is too large
Load Diff
@@ -21,16 +21,20 @@
|
||||
"bcryptjs": "3.0.2",
|
||||
"bullmq": "5.41.2",
|
||||
"compression": "1.8.1",
|
||||
"cookie-parser": "^1.4.7",
|
||||
"cors": "^2.8.5",
|
||||
"dockerode": "4.0.6",
|
||||
"dotenv": "^16.4.5",
|
||||
"express": "^4.19.2",
|
||||
"express-rate-limit": "8.0.1",
|
||||
"gamedig": "^5.3.1",
|
||||
"handlebars": "^4.7.8",
|
||||
"helmet": "^8.0.0",
|
||||
"ioredis": "^5.4.2",
|
||||
"isomorphic-dompurify": "^2.26.0",
|
||||
"jmespath": "^0.16.0",
|
||||
"joi": "^17.13.1",
|
||||
"jsdom": "^26.1.0",
|
||||
"jsonwebtoken": "9.0.2",
|
||||
"mailersend": "^2.2.0",
|
||||
"mjml": "^5.0.0-alpha.4",
|
||||
|
||||
@@ -4,11 +4,13 @@ import { responseHandler } from "./middleware/responseHandler.js";
|
||||
import cors from "cors";
|
||||
import helmet from "helmet";
|
||||
import compression from "compression";
|
||||
import cookieParser from "cookie-parser";
|
||||
import languageMiddleware from "./middleware/languageMiddleware.js";
|
||||
import swaggerUi from "swagger-ui-express";
|
||||
import { handleErrors } from "./middleware/handleErrors.js";
|
||||
import { setupRoutes } from "./config/routes.js";
|
||||
import { generalApiLimiter } from "./middleware/rateLimiter.js";
|
||||
import { sanitizeBody, sanitizeQuery } from "./middleware/sanitization.js";
|
||||
|
||||
export const createApp = ({ services, controllers, envSettings, frontendPath, openApiSpec }) => {
|
||||
const allowedOrigin = envSettings.clientHost;
|
||||
@@ -30,6 +32,11 @@ export const createApp = ({ services, controllers, envSettings, frontendPath, op
|
||||
})
|
||||
);
|
||||
app.use(express.json());
|
||||
app.use(cookieParser());
|
||||
|
||||
app.use(sanitizeBody());
|
||||
app.use(sanitizeQuery());
|
||||
|
||||
app.use(
|
||||
helmet({
|
||||
hsts: false,
|
||||
@@ -37,6 +44,9 @@ export const createApp = ({ services, controllers, envSettings, frontendPath, op
|
||||
useDefaults: true,
|
||||
directives: {
|
||||
upgradeInsecureRequests: null,
|
||||
"script-src": ["'self'", "'unsafe-inline'", "'unsafe-eval'"],
|
||||
"object-src": ["'none'"],
|
||||
"base-uri": ["'self'"],
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
@@ -31,6 +31,7 @@ const { compile } = pkg;
|
||||
import mjml2html from "mjml";
|
||||
import jwt from "jsonwebtoken";
|
||||
import crypto from "crypto";
|
||||
import { games } from "gamedig";
|
||||
|
||||
import { fileURLToPath } from "url";
|
||||
import { ObjectId } from "mongodb";
|
||||
@@ -201,6 +202,7 @@ export const initializeServices = async ({ logger, envSettings, settingsService
|
||||
papaparse,
|
||||
logger,
|
||||
errorService,
|
||||
games,
|
||||
});
|
||||
|
||||
const services = {
|
||||
|
||||
@@ -441,6 +441,17 @@ class MonitorController extends BaseController {
|
||||
SERVICE_NAME,
|
||||
"exportMonitorsToCSV"
|
||||
);
|
||||
|
||||
getAllGames = this.asyncHandler(
|
||||
async (req, res) => {
|
||||
return res.success({
|
||||
msg: "OK",
|
||||
data: this.monitorService.getAllGames(),
|
||||
});
|
||||
},
|
||||
SERVICE_NAME,
|
||||
"getAllGames"
|
||||
);
|
||||
}
|
||||
|
||||
export default MonitorController;
|
||||
|
||||
@@ -33,7 +33,7 @@ const MonitorSchema = mongoose.Schema(
|
||||
type: {
|
||||
type: String,
|
||||
required: true,
|
||||
enum: ["http", "ping", "pagespeed", "hardware", "docker", "port"],
|
||||
enum: ["http", "ping", "pagespeed", "hardware", "docker", "port", "game"],
|
||||
},
|
||||
ignoreTlsErrors: {
|
||||
type: Boolean,
|
||||
@@ -115,6 +115,9 @@ const MonitorSchema = mongoose.Schema(
|
||||
return this.alertThreshold;
|
||||
},
|
||||
},
|
||||
gameId: {
|
||||
type: String,
|
||||
},
|
||||
},
|
||||
{
|
||||
timestamps: true,
|
||||
|
||||
@@ -86,6 +86,7 @@ class CheckModule {
|
||||
port: this.Check,
|
||||
pagespeed: this.PageSpeedCheck,
|
||||
hardware: this.HardwareCheck,
|
||||
game: this.Check,
|
||||
};
|
||||
|
||||
const Model = checkModels[type];
|
||||
|
||||
@@ -310,7 +310,7 @@ class MonitorModule {
|
||||
checks: this.processChecksForDisplay(this.NormalizeData, checksForDateRange, numToDisplay, normalize),
|
||||
};
|
||||
|
||||
if (monitor.type === "http" || monitor.type === "ping" || monitor.type === "docker" || monitor.type === "port") {
|
||||
if (monitor.type === "http" || monitor.type === "ping" || monitor.type === "docker" || monitor.type === "port" || monitor.type === "game") {
|
||||
// HTTP/PING Specific stats
|
||||
monitorStats.periodAvgResponseTime = this.getAverageResponseTime(checksForDateRange);
|
||||
monitorStats.periodUptime = this.getUptimePercentage(checksForDateRange);
|
||||
|
||||
@@ -910,7 +910,7 @@ const buildGetMonitorsByTeamIdPipeline = (req) => {
|
||||
$switch: {
|
||||
branches: [
|
||||
{
|
||||
case: { $in: ["$type", ["http", "ping", "docker", "port"]] },
|
||||
case: { $in: ["$type", ["http", "ping", "docker", "port", "game"]] },
|
||||
then: "$standardchecks",
|
||||
},
|
||||
{
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
import { JSDOM } from "jsdom";
|
||||
import DOMPurify from "isomorphic-dompurify";
|
||||
|
||||
// Initialize DOMPurify with jsdom
|
||||
const window = new JSDOM("").window;
|
||||
const purify = DOMPurify(window);
|
||||
|
||||
/**
|
||||
* Sanitizes user input to prevent XSS attacks
|
||||
* @param {string} input - The input string to sanitize
|
||||
* @param {Object} options - Sanitization options
|
||||
* @returns {string} The sanitized string
|
||||
*/
|
||||
export const sanitizeInput = (input, options = {}) => {
|
||||
if (typeof input !== "string") {
|
||||
return input;
|
||||
}
|
||||
|
||||
// Default configuration - remove all HTML tags and attributes
|
||||
const defaultConfig = {
|
||||
ALLOWED_TAGS: [],
|
||||
ALLOWED_ATTR: [],
|
||||
KEEP_CONTENT: true,
|
||||
...options,
|
||||
};
|
||||
|
||||
return purify.sanitize(input, defaultConfig);
|
||||
};
|
||||
|
||||
/**
|
||||
* Sanitizes an object recursively
|
||||
* @param {Object} obj - The object to sanitize
|
||||
* @param {Object} options - Sanitization options
|
||||
* @returns {Object} The sanitized object
|
||||
*/
|
||||
export const sanitizeObject = (obj, options = {}) => {
|
||||
if (typeof obj !== "object" || obj === null) {
|
||||
return obj;
|
||||
}
|
||||
|
||||
if (Array.isArray(obj)) {
|
||||
return obj.map((item) => sanitizeObject(item, options));
|
||||
}
|
||||
|
||||
const sanitized = {};
|
||||
for (const [key, value] of Object.entries(obj)) {
|
||||
if (typeof value === "string") {
|
||||
sanitized[key] = sanitizeInput(value, options);
|
||||
} else if (typeof value === "object" && value !== null) {
|
||||
sanitized[key] = sanitizeObject(value, options);
|
||||
} else {
|
||||
sanitized[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
return sanitized;
|
||||
};
|
||||
|
||||
/**
|
||||
* Express middleware for sanitizing request body
|
||||
* @param {Object} options - Sanitization options
|
||||
* @returns {Function} Express middleware function
|
||||
*/
|
||||
export const sanitizeBody = (options = {}) => {
|
||||
return (req, res, next) => {
|
||||
if (req.body && typeof req.body === "object") {
|
||||
req.body = sanitizeObject(req.body, options);
|
||||
}
|
||||
next();
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Express middleware for sanitizing query parameters
|
||||
* @param {Object} options - Sanitization options
|
||||
* @returns {Function} Express middleware function
|
||||
*/
|
||||
export const sanitizeQuery = (options = {}) => {
|
||||
return (req, res, next) => {
|
||||
if (req.query && typeof req.query === "object") {
|
||||
req.query = sanitizeObject(req.query, options);
|
||||
}
|
||||
next();
|
||||
};
|
||||
};
|
||||
@@ -44,6 +44,7 @@ class MonitorRoutes {
|
||||
this.router.get("/export", isAllowed(["admin", "superadmin"]), this.monitorController.exportMonitorsToCSV);
|
||||
this.router.post("/bulk", isAllowed(["admin", "superadmin"]), upload.single("csvFile"), this.monitorController.createBulkMonitors);
|
||||
this.router.post("/test-email", isAllowed(["admin", "superadmin"]), this.monitorController.sendTestEmail);
|
||||
this.router.get("/games", this.monitorController.getAllGames);
|
||||
|
||||
// Individual monitor CRUD routes
|
||||
this.router.get("/:monitorId", this.monitorController.getMonitorById);
|
||||
|
||||
@@ -4,7 +4,7 @@ const SERVICE_NAME = "MonitorService";
|
||||
class MonitorService {
|
||||
static SERVICE_NAME = SERVICE_NAME;
|
||||
|
||||
constructor({ db, settingsService, jobQueue, stringService, emailService, papaparse, logger, errorService }) {
|
||||
constructor({ db, settingsService, jobQueue, stringService, emailService, papaparse, logger, errorService, games }) {
|
||||
this.db = db;
|
||||
this.settingsService = settingsService;
|
||||
this.jobQueue = jobQueue;
|
||||
@@ -13,6 +13,7 @@ class MonitorService {
|
||||
this.papaparse = papaparse;
|
||||
this.logger = logger;
|
||||
this.errorService = errorService;
|
||||
this.games = games;
|
||||
}
|
||||
|
||||
get serviceName() {
|
||||
@@ -183,7 +184,8 @@ class MonitorService {
|
||||
};
|
||||
|
||||
addDemoMonitors = async ({ userId, teamId }) => {
|
||||
const demoMonitors = await this.db.monitorModuleaddDemoMonitors(userId, teamId);
|
||||
const demoMonitors = await this.db.monitorModule.addDemoMonitors(userId, teamId);
|
||||
|
||||
await Promise.all(demoMonitors.map((monitor) => this.jobQueue.addJob(monitor._id, monitor)));
|
||||
return demoMonitors;
|
||||
};
|
||||
@@ -261,6 +263,10 @@ class MonitorService {
|
||||
const csv = this.papaparse.unparse(csvData);
|
||||
return csv;
|
||||
};
|
||||
|
||||
getAllGames = () => {
|
||||
return this.games;
|
||||
};
|
||||
}
|
||||
|
||||
export default MonitorService;
|
||||
|
||||
@@ -6,6 +6,7 @@ const TYPE_MAP = {
|
||||
docker: "checks",
|
||||
pagespeed: "pagespeedChecks",
|
||||
hardware: "hardwareChecks",
|
||||
game: "checks",
|
||||
};
|
||||
|
||||
class BufferService {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import jmespath from "jmespath";
|
||||
import https from "https";
|
||||
import { GameDig } from "gamedig";
|
||||
|
||||
const SERVICE_NAME = "NetworkService";
|
||||
const UPROCK_ENDPOINT = "https://api.uprock.com/checkmate/push";
|
||||
@@ -23,6 +24,7 @@ class NetworkService {
|
||||
this.TYPE_HARDWARE = "hardware";
|
||||
this.TYPE_DOCKER = "docker";
|
||||
this.TYPE_PORT = "port";
|
||||
this.TYPE_GAME = "game";
|
||||
this.SERVICE_NAME = SERVICE_NAME;
|
||||
this.NETWORK_ERROR = 5000;
|
||||
this.PING_ERROR = 5001;
|
||||
@@ -470,6 +472,62 @@ class NetworkService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests the status of a game monitor.
|
||||
*
|
||||
* @param {Object} monitor - The monitor object to request the status for.
|
||||
* @returns {Promise<Object>} The response from the game status request.
|
||||
* @throws {Error} Throws an error if the request fails or if the monitor is not configured correctly.
|
||||
* @property {string} monitorId - The ID of the monitor.
|
||||
* @property {string} type - The type of the monitor (should be "game").
|
||||
* @property {number} responseTime - The time taken for the request.
|
||||
* @property {Object|null} payload - The game state response or null if the request failed.
|
||||
* @property {boolean} status - Indicates if the request was successful (true) or not (false).
|
||||
* @property {number} code - The status code of the request (200 for success, NETWORK_ERROR for failure).
|
||||
* @property {string} message - A message indicating the result of the request.
|
||||
*/
|
||||
async requestGame(monitor) {
|
||||
try {
|
||||
const { url, port, gameId } = monitor;
|
||||
|
||||
const gameResponse = {
|
||||
code: 200,
|
||||
status: true,
|
||||
message: "Success",
|
||||
monitorId: monitor._id,
|
||||
type: "game",
|
||||
};
|
||||
|
||||
const state = await GameDig.query({
|
||||
type: gameId,
|
||||
host: url,
|
||||
port: port,
|
||||
}).catch((error) => {
|
||||
this.logger.warn({
|
||||
message: error.message,
|
||||
service: this.SERVICE_NAME,
|
||||
method: "requestGame",
|
||||
details: { url, port, gameId },
|
||||
});
|
||||
});
|
||||
|
||||
if (!state) {
|
||||
gameResponse.status = false;
|
||||
gameResponse.code = this.NETWORK_ERROR;
|
||||
gameResponse.message = "No response";
|
||||
return gameResponse;
|
||||
}
|
||||
|
||||
gameResponse.responseTime = state.ping;
|
||||
gameResponse.payload = state;
|
||||
return gameResponse;
|
||||
} catch (error) {
|
||||
error.service = this.SERVICE_NAME;
|
||||
error.method = "requestPing";
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the status of a job based on its type and returns the appropriate response.
|
||||
*
|
||||
@@ -494,6 +552,8 @@ class NetworkService {
|
||||
return await this.requestDocker(monitor);
|
||||
case this.TYPE_PORT:
|
||||
return await this.requestPort(monitor);
|
||||
case this.TYPE_GAME:
|
||||
return await this.requestGame(monitor);
|
||||
default:
|
||||
return this.handleUnsupportedType(type);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* Get standardized cookie options for authentication tokens
|
||||
* @param {Object} options - Additional cookie options
|
||||
* @returns {Object} Cookie options object
|
||||
*/
|
||||
export const getAuthCookieOptions = (options = {}) => {
|
||||
return {
|
||||
httpOnly: true,
|
||||
secure: process.env.NODE_ENV === "production",
|
||||
sameSite: "strict",
|
||||
maxAge: 2 * 60 * 60 * 1000, // 2 hours (matches JWT TTL)
|
||||
...options,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Clear cookie options for authentication tokens
|
||||
* @returns {Object} Cookie clear options object
|
||||
*/
|
||||
export const getClearAuthCookieOptions = () => {
|
||||
return {
|
||||
httpOnly: true,
|
||||
secure: process.env.NODE_ENV === "production",
|
||||
sameSite: "strict",
|
||||
};
|
||||
};
|
||||
@@ -124,8 +124,8 @@ const getMonitorsByTeamIdQueryValidation = joi.object({
|
||||
type: joi
|
||||
.alternatives()
|
||||
.try(
|
||||
joi.string().valid("http", "ping", "pagespeed", "docker", "hardware", "port"),
|
||||
joi.array().items(joi.string().valid("http", "ping", "pagespeed", "docker", "hardware", "port"))
|
||||
joi.string().valid("http", "ping", "pagespeed", "docker", "hardware", "port", "game"),
|
||||
joi.array().items(joi.string().valid("http", "ping", "pagespeed", "docker", "hardware", "port", "game"))
|
||||
),
|
||||
page: joi.number(),
|
||||
rowsPerPage: joi.number(),
|
||||
@@ -171,6 +171,7 @@ const createMonitorBodyValidation = joi.object({
|
||||
jsonPath: joi.string().allow(""),
|
||||
expectedValue: joi.string().allow(""),
|
||||
matchMethod: joi.string(),
|
||||
gameId: joi.string().allow(""),
|
||||
});
|
||||
|
||||
const createMonitorsBodyValidation = joi.array().items(
|
||||
@@ -197,6 +198,7 @@ const editMonitorBodyValidation = joi.object({
|
||||
usage_disk: joi.number(),
|
||||
usage_temperature: joi.number(),
|
||||
}),
|
||||
gameId: joi.string(),
|
||||
});
|
||||
|
||||
const pauseMonitorParamValidation = joi.object({
|
||||
@@ -294,7 +296,7 @@ const getChecksParamValidation = joi.object({
|
||||
});
|
||||
|
||||
const getChecksQueryValidation = joi.object({
|
||||
type: joi.string().valid("http", "ping", "pagespeed", "hardware", "docker", "port"),
|
||||
type: joi.string().valid("http", "ping", "pagespeed", "hardware", "docker", "port", "game"),
|
||||
sortOrder: joi.string().valid("asc", "desc"),
|
||||
limit: joi.number(),
|
||||
dateRange: joi.string().valid("recent", "hour", "day", "week", "month", "all"),
|
||||
|
||||
Reference in New Issue
Block a user