From ff2f84d46bb859e3d6a5eaa4de81855793da3395 Mon Sep 17 00:00:00 2001 From: ppmar Date: Wed, 17 Dec 2025 23:50:40 +0100 Subject: [PATCH] resolve disk selection persistence and validation issues --- README.md | 1 + client/src/Hooks/v1/monitorHooks.js | 1 + .../Create/Components/DiskSelection.jsx | 57 ++++++------- .../Create/hooks/useInfrastructureSubmit.jsx | 4 +- .../Pages/v1/Infrastructure/Create/index.jsx | 74 ++++++++--------- .../Details/Components/GaugeBoxes/index.jsx | 35 ++++---- .../Details/Hooks/useHardwareUtils.jsx | 81 +++++++++++++++++-- client/src/Validation/validation.js | 77 +++++++++--------- client/src/locales/de.json | 9 ++- client/src/locales/fr.json | 9 ++- client/src/locales/ja.json | 9 ++- client/src/locales/pt-BR.json | 9 ++- client/src/locales/ru.json | 9 ++- client/src/locales/th.json | 9 ++- client/src/locales/zh-CN.json | 9 ++- docs/infrastructure-disk-selection.md | 74 +++++++++++++++++ 16 files changed, 331 insertions(+), 136 deletions(-) create mode 100644 docs/infrastructure-disk-selection.md diff --git a/README.md b/README.md index 4fc1dadd1..129953fb9 100644 --- a/README.md +++ b/README.md @@ -91,6 +91,7 @@ Feel free to ask questions or share your ideas - we'd love to hear from you! - Website monitoring - Page speed monitoring - Infrastructure monitoring (memory, disk usage, CPU performance, network etc) - requires [Capture](https://github.com/bluewave-labs/capture) agent + - Selective disk monitoring with mountpoint selection - Docker monitoring - Ping monitoring - SSL monitoring diff --git a/client/src/Hooks/v1/monitorHooks.js b/client/src/Hooks/v1/monitorHooks.js index 9ee98cc55..8acbea608 100644 --- a/client/src/Hooks/v1/monitorHooks.js +++ b/client/src/Hooks/v1/monitorHooks.js @@ -387,6 +387,7 @@ const useUpdateMonitor = () => { ...(monitor.type === "hardware" && { thresholds: monitor.thresholds, secret: monitor.secret, + selectedDisks: monitor.selectedDisks, }), }; await networkService.updateMonitor({ diff --git a/client/src/Pages/v1/Infrastructure/Create/Components/DiskSelection.jsx b/client/src/Pages/v1/Infrastructure/Create/Components/DiskSelection.jsx index 7bf1538c5..88b56f966 100644 --- a/client/src/Pages/v1/Infrastructure/Create/Components/DiskSelection.jsx +++ b/client/src/Pages/v1/Infrastructure/Create/Components/DiskSelection.jsx @@ -11,7 +11,6 @@ const DiskSelection = ({ availableDisks, selectedDisks, onChange }) => { const { t } = useTranslation(); const handleDiskChange = (event, mountpoint) => { - // Le composant Checkbox personnalisé renvoie l'event natif const isChecked = event.target.checked; let newSelectedDisks = []; @@ -37,42 +36,38 @@ const DiskSelection = ({ availableDisks, selectedDisks, onChange }) => { {(!availableDisks || availableDisks.length === 0) ? ( - {t("v1.infrastructure.disk_selection_info")} ) : ( - availableDisks.map((disk) => ( - /* Reproduction exacte de la structure de CustomThreshold - pour garantir l'alignement parfait avec la section Alertes - */ - - { + const identifier = disk.mountpoint || disk.device; + return ( + - handleDiskChange(e, disk.mountpoint)} - /> - - {/* Pas de deuxième Stack ici car nous n'avons pas besoin - du champ TextInput pour la sélection simple - */} - - )) + + handleDiskChange(e, identifier)} + /> + + + ) + }) )} diff --git a/client/src/Pages/v1/Infrastructure/Create/hooks/useInfrastructureSubmit.jsx b/client/src/Pages/v1/Infrastructure/Create/hooks/useInfrastructureSubmit.jsx index 8a97486c7..15a5f3c5d 100644 --- a/client/src/Pages/v1/Infrastructure/Create/hooks/useInfrastructureSubmit.jsx +++ b/client/src/Pages/v1/Infrastructure/Create/hooks/useInfrastructureSubmit.jsx @@ -29,11 +29,11 @@ const useInfrastructureSubmit = () => { ...(infrastructureMonitor.disk ? { usage_disk: infrastructureMonitor.usage_disk } : {}), - temperature: infrastructureMonitor.temperature, ...(infrastructureMonitor.temperature ? { usage_temperature: infrastructureMonitor.usage_temperature } : {}), secret: infrastructureMonitor.secret, + selectedDisks: infrastructureMonitor.selectedDisks, }; return form; }; @@ -52,6 +52,7 @@ const useInfrastructureSubmit = () => { usage_disk, temperature, usage_temperature, + selectedDisks, ...rest } = form; @@ -68,6 +69,7 @@ const useInfrastructureSubmit = () => { description: form.name, type: "hardware", notifications: infrastructureMonitor.notifications, + selectedDisks, thresholds, }; // Handle create or update diff --git a/client/src/Pages/v1/Infrastructure/Create/index.jsx b/client/src/Pages/v1/Infrastructure/Create/index.jsx index bcc644363..b02cd36cc 100644 --- a/client/src/Pages/v1/Infrastructure/Create/index.jsx +++ b/client/src/Pages/v1/Infrastructure/Create/index.jsx @@ -14,7 +14,7 @@ import DiskSelection from "./Components/DiskSelection.jsx"; // Utils import NotificationsConfig from "@/Components/v1/NotificationConfig/index.jsx"; import { useGetNotificationsByTeamId } from "../../../../Hooks/v1/useNotifications.js"; -import NetworkService from "../../../../Utils/NetworkService"; +import { networkService } from "../../../../Utils/NetworkService"; import { useParams } from "react-router-dom"; import { useState, useEffect } from "react"; import { useTheme } from "@emotion/react"; @@ -75,9 +75,9 @@ const CreateInfrastructureMonitor = () => { ...(isCreate ? [{ name: "Create", path: "/infrastructure/create" }] : [ - { name: "Details", path: `/infrastructure/${monitorId}` }, - { name: "Configure", path: `/infrastructure/configure/${monitorId}` }, - ]), + { name: "Details", path: `/infrastructure/${monitorId}` }, + { name: "Configure", path: `/infrastructure/configure/${monitorId}` }, + ]), ]; // Populate form fields if editing useEffect(() => { @@ -89,23 +89,30 @@ const CreateInfrastructureMonitor = () => { setHttps(monitor.url.startsWith("https")); initializeInfrastructureMonitorForUpdate(monitor); const fetchLastCheck = async () => { - try { - const params = { - dateRange: "recent", - limit: 1, - sortOrder: "desc", - }; - // On utilise NetworkService directement - const response = await NetworkService.get(`/api/v1/checks/${monitorId}`, { params }); - const disks = response?.data?.checks?.[0]?.payload?.disks || []; - setAvailableDisks(disks); - } catch (error) { - console.error("Erreur pendant la récupération des disques:", error); - setAvailableDisks([]); // En cas d'erreur, on met un tableau vide - } - }; + try { + const { stats } = monitor ?? {}; + let latestCheck = stats?.aggregateData?.latestCheck; + let disks = latestCheck?.disk || []; - fetchLastCheck(); + if (disks.length > 0) { + setAvailableDisks(disks); + return; + } + + const response = await networkService.getChecksByMonitor({ + monitorId, + dateRange: "all", + rowsPerPage: 1, + sortOrder: "desc", + }); + disks = response?.data?.data?.checks?.[0]?.disk || []; + setAvailableDisks(disks); + } catch (error) { + setAvailableDisks([]); + } + }; + + fetchLastCheck(); } }, [ isCreate, @@ -154,14 +161,7 @@ const CreateInfrastructureMonitor = () => { isPausing || notificationsAreLoading; - // --- AJOUT TEMPORAIRE : Fausses données --- - const MOCK_DISKS = [ - { mountpoint: "C:" }, - { mountpoint: "D:" }, - { mountpoint: "/mnt/data" }, - { mountpoint: "/var/lib/docker" } - ]; - // ----------------------------------------- + return ( @@ -359,15 +359,15 @@ const CreateInfrastructureMonitor = () => { /> {monitorId && ( - - onChangeForm("selectedDisks", newSelectedDisks) - } - /> - )} - + + onChangeForm("selectedDisks", newSelectedDisks) + } + /> + )} + { metricTwo: t("frequency"), valueTwo: `${(cpuFrequency / 1000).toFixed(2)} Ghz`, }, - ...(latestCheck?.disk ?? []).map((disk, idx) => ({ - type: "disk", - diskIndex: idx, - value: decimalToPercentage(disk.usage_percent), - heading: `Disk${idx} usage`, - metricOne: t("used"), - valueOne: formatBytes(disk.total_bytes - disk.free_bytes, true), - metricTwo: t("total"), - valueTwo: formatBytes(disk.total_bytes, true), - metricThree: t("device"), - valueThree: formatDeviceName(disk.device), - metricFour: t("mountpoint"), - valueFour: formatMountpoint(disk.mountpoint), - })), + ...(latestCheck?.disk ?? []) + .filter((disk) => { + if (!monitor?.selectedDisks || monitor.selectedDisks.length === 0) { + return true; + } + return monitor.selectedDisks.includes(disk.mountpoint || disk.device); + }) + .map((disk, idx) => ({ + type: "disk", + diskIndex: idx, + value: decimalToPercentage(disk.usage_percent), + heading: `Disk${idx} usage`, + metricOne: t("used"), + valueOne: formatBytes(disk.total_bytes - disk.free_bytes, true), + metricTwo: t("total"), + valueTwo: formatBytes(disk.total_bytes, true), + metricThree: t("device"), + valueThree: formatDeviceName(disk.device), + metricFour: t("mountpoint"), + valueFour: formatMountpoint(disk.mountpoint), + })), ]; return ( diff --git a/client/src/Pages/v1/Infrastructure/Details/Hooks/useHardwareUtils.jsx b/client/src/Pages/v1/Infrastructure/Details/Hooks/useHardwareUtils.jsx index f2220bd75..80efb21b9 100644 --- a/client/src/Pages/v1/Infrastructure/Details/Hooks/useHardwareUtils.jsx +++ b/client/src/Pages/v1/Infrastructure/Details/Hooks/useHardwareUtils.jsx @@ -1,4 +1,4 @@ -import { Typography } from "@mui/material"; +import { Typography, Tooltip } from "@mui/material"; import { useTheme } from "@emotion/react"; import { useTranslation } from "react-i18next"; @@ -121,19 +121,84 @@ const useHardwareUtils = () => { }; const formatDeviceName = (device) => { + const deviceStr = String(device || ''); + + // Extract the last part of the path (after last '/') + const parts = deviceStr.split('/'); + const lastPart = parts[parts.length - 1]; + + // If there's more than one part, show with "..." prefix + const displayText = parts.length > 1 ? `.../${lastPart}` : deviceStr; + + // Always show tooltip with full device path return ( - <> - {String(device)} - + + + {displayText} + + ); }; const formatMountpoint = (mountpoint) => { + const mountpointStr = String(mountpoint || ''); + + if (!mountpointStr) { + return ( + + + N/A + + + ); + } + + // Extract the last part of the path (after last '/') + const parts = mountpointStr.split('/'); + const lastPart = parts[parts.length - 1]; + + // If there's more than one part, show with "..." prefix + const displayText = parts.length > 1 ? `.../${lastPart}` : mountpointStr; + + // Always show tooltip with full mountpoint path return ( - <> - {String(mountpoint)} - - ) + + + {displayText} + + + ); } /** diff --git a/client/src/Validation/validation.js b/client/src/Validation/validation.js index ce50d96c1..dfd5639ce 100644 --- a/client/src/Validation/validation.js +++ b/client/src/Validation/validation.js @@ -144,44 +144,44 @@ const monitorValidation = joi.object({ // Regex from https://gist.github.com/dperini/729294 var urlRegex = new RegExp( "^" + - // protocol identifier (optional) - // short syntax // still required - "(?:(?:https?|ftp):\\/\\/)?" + - // user:pass BasicAuth (optional) - "(?:" + - // IP address dotted notation octets - // excludes loopback network 0.0.0.0 - // excludes reserved space >= 224.0.0.0 - // excludes network & broadcast addresses - // (first & last IP address of each class) - "(?:[1-9]\\d?|1\\d\\d|2[01]\\d|22[0-3])" + - "(?:\\.(?:1?\\d{1,2}|2[0-4]\\d|25[0-5])){2}" + - "(?:\\.(?:[1-9]\\d?|1\\d\\d|2[0-4]\\d|25[0-4]))" + - "|" + - // host & domain names, may end with dot - // can be replaced by a shortest alternative - // (?![-_])(?:[-\\w\\u00a1-\\uffff]{0,63}[^-_]\\.)+ - "(?:" + - // Single hostname without dots (like localhost) - "[a-z0-9\\u00a1-\\uffff][a-z0-9\\u00a1-\\uffff_-]{0,62}" + - "|" + - // Domain with dots - "(?:" + - "(?:" + - "[a-z0-9\\u00a1-\\uffff]" + - "[a-z0-9\\u00a1-\\uffff_-]{0,62}" + - ")?" + - "[a-z0-9\\u00a1-\\uffff]\\." + - ")+" + - // TLD identifier name, may end with dot - "(?:[a-z\\u00a1-\\uffff]{2,}\\.?)" + - ")" + - ")" + - // port number (optional) - "(?::\\d{2,5})?" + - // resource path (optional) - "(?:[/?#]\\S*)?" + - "$", + // protocol identifier (optional) + // short syntax // still required + "(?:(?:https?|ftp):\\/\\/)?" + + // user:pass BasicAuth (optional) + "(?:" + + // IP address dotted notation octets + // excludes loopback network 0.0.0.0 + // excludes reserved space >= 224.0.0.0 + // excludes network & broadcast addresses + // (first & last IP address of each class) + "(?:[1-9]\\d?|1\\d\\d|2[01]\\d|22[0-3])" + + "(?:\\.(?:1?\\d{1,2}|2[0-4]\\d|25[0-5])){2}" + + "(?:\\.(?:[1-9]\\d?|1\\d\\d|2[0-4]\\d|25[0-4]))" + + "|" + + // host & domain names, may end with dot + // can be replaced by a shortest alternative + // (?![-_])(?:[-\\w\\u00a1-\\uffff]{0,63}[^-_]\\.)+ + "(?:" + + // Single hostname without dots (like localhost) + "[a-z0-9\\u00a1-\\uffff][a-z0-9\\u00a1-\\uffff_-]{0,62}" + + "|" + + // Domain with dots + "(?:" + + "(?:" + + "[a-z0-9\\u00a1-\\uffff]" + + "[a-z0-9\\u00a1-\\uffff_-]{0,62}" + + ")?" + + "[a-z0-9\\u00a1-\\uffff]\\." + + ")+" + + // TLD identifier name, may end with dot + "(?:[a-z\\u00a1-\\uffff]{2,}\\.?)" + + ")" + + ")" + + // port number (optional) + "(?::\\d{2,5})?" + + // resource path (optional) + "(?:[/?#]\\S*)?" + + "$", "i" ); if (!urlRegex.test(value)) { @@ -448,6 +448,7 @@ const infrastructureMonitorValidation = joi.object({ "number.max": "Status window threshold cannot exceed 100%.", }), notifications: joi.array().items(joi.string()), + selectedDisks: joi.array().items(joi.string()).optional(), }); const notificationValidation = joi.object({ diff --git a/client/src/locales/de.json b/client/src/locales/de.json index da5a3ee61..fd3918378 100644 --- a/client/src/locales/de.json +++ b/client/src/locales/de.json @@ -1129,5 +1129,12 @@ "packetsReceivedRate": "Rate empfangener Pakete", "packetsSent": "Gesendete Pakete", "rate": "Rate", - "selectInterface": "Schnittstelle auswählen" + "selectInterface": "Schnittstelle auswählen", + "v1": { + "infrastructure": { + "disk_selection_title": "Festplattenauswahl", + "disk_selection_info": "Momentan wurde keine Festplatte erkannt.", + "disk_selection_description": "Wählen Sie die spezifischen Festplatten aus, die Sie überwachen möchten." + } + } } diff --git a/client/src/locales/fr.json b/client/src/locales/fr.json index 1ebc8bad4..9e76440f0 100644 --- a/client/src/locales/fr.json +++ b/client/src/locales/fr.json @@ -1129,5 +1129,12 @@ "packetsReceivedRate": "", "packetsSent": "", "rate": "", - "selectInterface": "" + "selectInterface": "", + "v1": { + "infrastructure": { + "disk_selection_title": "Sélection des disques", + "disk_selection_info": "Aucun disque détecté pour le moment.", + "disk_selection_description": "Sélectionnez les disques spécifiques que vous souhaitez surveiller." + } + } } diff --git a/client/src/locales/ja.json b/client/src/locales/ja.json index f458b8ed0..68d94139f 100644 --- a/client/src/locales/ja.json +++ b/client/src/locales/ja.json @@ -1108,5 +1108,12 @@ "packetsReceivedRate": "", "packetsSent": "", "rate": "", - "selectInterface": "" + "selectInterface": "", + "v1": { + "infrastructure": { + "disk_selection_title": "ディスク選択", + "disk_selection_info": "現在ディスクが検出されていません。", + "disk_selection_description": "監視したい特定のディスクを選択してください。" + } + } } diff --git a/client/src/locales/pt-BR.json b/client/src/locales/pt-BR.json index 1a6d9a62f..193c53788 100644 --- a/client/src/locales/pt-BR.json +++ b/client/src/locales/pt-BR.json @@ -1129,5 +1129,12 @@ "packetsReceivedRate": "", "packetsSent": "", "rate": "", - "selectInterface": "" + "selectInterface": "", + "v1": { + "infrastructure": { + "disk_selection_title": "Seleção de Discos", + "disk_selection_info": "Nenhum disco detectado no momento.", + "disk_selection_description": "Selecione os discos específicos que você deseja monitorar." + } + } } diff --git a/client/src/locales/ru.json b/client/src/locales/ru.json index cdb4d235d..ab40ecb0d 100644 --- a/client/src/locales/ru.json +++ b/client/src/locales/ru.json @@ -1129,5 +1129,12 @@ "packetsReceivedRate": "Скорость получения пакетов", "packetsSent": "Отправлено пакетов", "rate": "коэффициент", - "selectInterface": "Выберите интерфейс" + "selectInterface": "Выберите интерфейс", + "v1": { + "infrastructure": { + "disk_selection_title": "Выбор дисков", + "disk_selection_info": "На данный момент диск не обнаружен.", + "disk_selection_description": "Выберите конкретные диски, которые вы хотите отслеживать." + } + } } diff --git a/client/src/locales/th.json b/client/src/locales/th.json index c32849afe..d5f9ad137 100644 --- a/client/src/locales/th.json +++ b/client/src/locales/th.json @@ -1129,5 +1129,12 @@ "packetsReceivedRate": "อัตราการรับแพ็กเก็ต", "packetsSent": "แพ็กเก็ตที่ส่ง", "rate": "อัตรา", - "selectInterface": "เลือกอินเทอร์เฟซ" + "selectInterface": "เลือกอินเทอร์เฟซ", + "v1": { + "infrastructure": { + "disk_selection_title": "การเลือกดิสก์", + "disk_selection_info": "ไม่พบดิสก์ในขณะนี้", + "disk_selection_description": "เลือกดิสก์ที่คุณต้องการตรวจสอบ" + } + } } diff --git a/client/src/locales/zh-CN.json b/client/src/locales/zh-CN.json index 305cefe87..67b419445 100644 --- a/client/src/locales/zh-CN.json +++ b/client/src/locales/zh-CN.json @@ -1121,5 +1121,12 @@ "packetsReceivedRate": "数据包接收率", "packetsSent": "已发送数据包", "rate": "速度", - "selectInterface": "选择接口" + "selectInterface": "选择接口", + "v1": { + "infrastructure": { + "disk_selection_title": "磁盘选择", + "disk_selection_info": "目前没有检测到磁盘。", + "disk_selection_description": "选择您要监控的特定磁盘。" + } + } } diff --git a/docs/infrastructure-disk-selection.md b/docs/infrastructure-disk-selection.md new file mode 100644 index 000000000..500e3be1e --- /dev/null +++ b/docs/infrastructure-disk-selection.md @@ -0,0 +1,74 @@ +# Infrastructure Monitoring - Disk Selection + +This guide explains how to use the selective disk monitoring feature in Checkmate's infrastructure monitoring. + +## Overview + +By default, Checkmate monitors all detected disks on your server. The disk selection feature allows you to choose specific disks/mountpoints to monitor, giving you more control over what gets tracked and displayed. + +## Prerequisites + +- Checkmate server running with [Capture agent](https://github.com/bluewave-labs/capture) installed on target server +- An existing infrastructure monitor already created and configured +- At least one disk detected by the Capture agent + +## Using Disk Selection + +### Accessing the Feature + +1. Navigate to your Infrastructure monitors page +2. Click on an existing infrastructure monitor to view details +3. Click the "Configure" or "Edit" button +4. Scroll down to the **Disk Selection** section + +> **Note:** The disk selection feature is only available when editing existing monitors that have already detected disks. + +### Selecting Disks to Monitor + +1. **View Available Disks**: The system automatically detects all disks/mountpoints from your server +2. **Select Disks**: Check the boxes next to the disks you want to monitor +3. **Apply Changes**: Click "Save" to update your monitor configuration + +### Understanding Disk Identifiers + +Disks are identified by their mountpoint or device name: +- **Mountpoint**: `/` (root), `/home`, `/var`, etc. +- **Device**: `/dev/sda1`, `/dev/nvme0n1p1`, etc. + +### Behavior + +- **All disks selected**: Shows all detected disks in gauges and charts +- **Specific disks selected**: Only shows selected disks in the monitoring interface +- **No disks detected**: Displays "No disk detected for the moment" message + +## What Gets Filtered + +When you select specific disks, the following elements are filtered: + +- **Disk Usage Gauges**: Only selected disks appear in the dashboard +- **Disk Usage Charts**: Time-series charts show only selected disks +- **Monitor Table**: Summary view reflects only selected disk usage + +## Example Use Cases + +- **Server with multiple drives**: Monitor only critical system disks, ignore temporary storage +- **Database servers**: Focus monitoring on database partition while ignoring logs partition +- **Web servers**: Monitor web content disk separately from system disk +- **Development environments**: Track only project-specific mountpoints + +## Troubleshooting + +### No Disks Appearing +- Verify the Capture agent is running on the target server +- Check that the agent has proper permissions to read disk information +- Ensure the monitor has been running long enough to collect at least one check + +### Missing Disk Information +- Some disks may not be accessible to the Capture agent due to permissions +- Virtual or network-mounted disks may not appear in the list +- Check the Capture agent logs for any disk detection errors + +## Related Documentation + +- [Infrastructure Monitoring Setup](https://docs.checkmate.so/checkmate-2.1) +- [Capture Agent Installation](https://github.com/bluewave-labs/capture) \ No newline at end of file