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