mirror of
https://github.com/jokob-sk/NetAlertX.git
synced 2026-10-01 18:25:06 -04:00
BE+FE: System info -> Performance section
This commit is contained in:
1 parent
491b60b49e
commit
4aa2a0dddb
51 files changed
+1548
-97
No files matched your search
+21
-1
@@ -16,7 +16,7 @@ The application performs regular maintenance and database cleanup. If these task
|
||||
|
||||
### Database and Log File Size
|
||||
|
||||
A large database or oversized log files can impact performance. You can check database and table sizes on the **Maintenance** page.
|
||||
A large database or oversized log files can impact performance. You can check database and table sizes on the **System Info → Storage** page.
|
||||
|
||||

|
||||
|
||||
@@ -32,6 +32,26 @@ Please note that excessively large log files will increase memory consumption. D
|
||||
|
||||
---
|
||||
|
||||
## Tracking NetAlertX's Own Resource Usage Over Time
|
||||
|
||||
The **System Info → Performance** page graphs NetAlertX's own process CPU%, memory, disk I/O, and scan duration over hour/day/week/month ranges, sampled once per scan cycle. This turns "did a recent change make things slower" into a question you can answer by looking at real history, instead of guessing from what a change *should* cost.
|
||||
|
||||
**What each chart shows:**
|
||||
|
||||
* **CPU %** - average CPU utilisation over the scan cycle (`100%` = one fully-utilized CPU core; can exceed `100%` with multi-threaded work). This is an average over the whole cycle, not an instantaneous reading - a short burst of high usage followed by idle time reads as a lower percentage than the peak.
|
||||
* **Memory Usage** - resident memory (RSS - the physical RAM the process currently holds), in MB, sampled once per cycle.
|
||||
* **IO Read / IO Write** - bytes read/written by the NetAlertX process itself during that cycle, in MB. This only ever reflects NetAlertX's own direct I/O (its own DB/cache writes) - a plugin's own subprocess doing heavy disk I/O (e.g. a scanner writing its own result file) is not captured; this is a structural limitation of how the OS attributes child-process I/O to the parent, not a gap that can be closed later.
|
||||
* **Scan Duration** - wall-clock time the scan cycle took, in seconds.
|
||||
|
||||
> [!NOTE]
|
||||
> The IO numbers come from the kernel's own per-process I/O accounting (`/proc/<pid>/io`), which the kernel itself documents as an approximation. On some Docker storage drivers this can be badly inaccurate - confirmed on the deprecated `aufs` driver (common on older Synology NAS Docker setups), where union-filesystem copy-up writes get mis-attributed and can report write volumes many times larger than what's actually reaching disk (`docker info | grep -i "storage driver"` shows which driver you're on; `aufs` is a known-bad case, alongside its own `blkio throttle` warnings confirming the kernel's block-I/O accounting isn't properly wired up for it). If the IO chart shows large, consistent write numbers that don't match actual database growth (check the DB/WAL size on **System Info → Storage**), that's this known driver limitation, not a real cost. Newer Docker storage drivers (`overlay2`) don't have this problem.
|
||||
|
||||
* Controlled by the `MAINT_PERF_DAYS` setting (part of the `MAINT` plugin) - the number of days of history to retain, `0` disables collection. Defaults to `30`.
|
||||
* History cannot be backfilled - data starts accumulating from whenever collection is enabled.
|
||||
* `GET /health` also exposes live `process_cpu_pct`/`process_rss_mb` fields for external monitoring, independent of this history.
|
||||
|
||||
---
|
||||
|
||||
## Maintenance Plugins
|
||||
|
||||
Two plugins help maintain the system’s performance:
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 61 KiB After Width: | Height: | Size: 33 KiB |
+1
-1
@@ -2597,7 +2597,7 @@ textarea[readonly],
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#panInitCheck
|
||||
#panInitCheck.active
|
||||
{
|
||||
display: flow-root;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,162 @@
|
||||
// Chart.js instances, kept so a range switch destroys the old chart before
|
||||
// building a new one on the same canvas (Chart.js throws otherwise).
|
||||
var resourceHistoryChartInstances = {};
|
||||
|
||||
/**
|
||||
* Fetches Resource_History data for the given range ('hour'/'day'/'week'/'month')
|
||||
* and renders the four Performance-tab charts, or shows the disabled-state
|
||||
* message when no rows exist yet (collection off, or just enabled).
|
||||
*/
|
||||
function initResourceHistoryGraphs(range) {
|
||||
$.get('php/server/query_json.php', { file: `table_resource_history_${range}.json`, nocache: Date.now() }, function (res) {
|
||||
var rows = (res && res.data) ? res.data : [];
|
||||
|
||||
if (rows.length === 0) {
|
||||
$('#resourceHistoryCharts').addClass('myhidden');
|
||||
$('#resourceHistoryDisabledMessage').removeClass('myhidden');
|
||||
return;
|
||||
}
|
||||
|
||||
$('#resourceHistoryDisabledMessage').addClass('myhidden');
|
||||
$('#resourceHistoryCharts').removeClass('myhidden');
|
||||
|
||||
var labels = [];
|
||||
var cpuData = [];
|
||||
var rssData = [];
|
||||
var ioReadData = [];
|
||||
var ioWriteData = [];
|
||||
var durationData = [];
|
||||
var tickFailed = [];
|
||||
|
||||
function round2(n) {
|
||||
return n == null ? null : Math.round(n * 100) / 100;
|
||||
}
|
||||
|
||||
rows.forEach(function (entry) {
|
||||
// hour/day rows carry resDateTime (raw); week/month rows carry bucket
|
||||
// (hourly rollup) instead - see const.py's sql_resource_history_* queries.
|
||||
var ts = entry.resDateTime || entry.bucket;
|
||||
labels.push(localizeTimestamp(ts).slice(0, 16));
|
||||
cpuData.push(round2(entry.resCpuPercent));
|
||||
rssData.push(round2(entry.resRssMb));
|
||||
// Bytes -> MB and ms -> s: raw units from the DB aren't a readable axis scale.
|
||||
ioReadData.push(entry.resIoReadBytes != null ? round2(entry.resIoReadBytes / (1024 * 1024)) : null);
|
||||
ioWriteData.push(entry.resIoWriteBytes != null ? round2(entry.resIoWriteBytes / (1024 * 1024)) : null);
|
||||
durationData.push(entry.resScanDurationMs != null ? round2(entry.resScanDurationMs / 1000) : null);
|
||||
tickFailed.push(entry.resTickFailed == 1);
|
||||
});
|
||||
|
||||
renderResourceHistoryCharts(labels, cpuData, rssData, ioReadData, ioWriteData, durationData, tickFailed);
|
||||
}).fail(function () {
|
||||
console.error('Error fetching resource history data.');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds/replaces the four small-multiple Resource_History charts (CPU%,
|
||||
* memory usage MB, IO read+write MB, scan duration seconds) on the same time
|
||||
* axis. Rows written from the tick-failure `finally` path (resTickFailed = 1)
|
||||
* are rendered as visually distinct points rather than plain data, since
|
||||
* their numbers may reflect a truncated, crash-adjacent sample.
|
||||
*/
|
||||
function renderResourceHistoryCharts(labels, cpuData, rssData, ioReadData, ioWriteData, durationData, tickFailed) {
|
||||
var normalColor = "rgba(0, 166, 89, .8)";
|
||||
var failedColor = "#dd4b39";
|
||||
|
||||
function pointColors(baseColor) {
|
||||
return tickFailed.map(function (failed) { return failed ? failedColor : baseColor; });
|
||||
}
|
||||
|
||||
function destroyIfExists(key) {
|
||||
if (resourceHistoryChartInstances[key]) {
|
||||
resourceHistoryChartInstances[key].destroy();
|
||||
}
|
||||
}
|
||||
|
||||
var commonScales = {
|
||||
yAxes: [{
|
||||
ticks: { beginAtZero: true, fontColor: '#A0A0A0' },
|
||||
gridLines: { color: "rgba(0, 0, 0, 0)" },
|
||||
}],
|
||||
xAxes: [{
|
||||
ticks: { fontColor: '#A0A0A0' },
|
||||
gridLines: { color: "rgba(0, 0, 0, 0)" },
|
||||
}],
|
||||
};
|
||||
|
||||
destroyIfExists('cpu');
|
||||
resourceHistoryChartInstances.cpu = new Chart("ResourceCpuChart", {
|
||||
type: "line",
|
||||
data: {
|
||||
labels: labels,
|
||||
datasets: [{
|
||||
label: getString("Systeminfo_Performance_CPU"),
|
||||
data: cpuData,
|
||||
borderColor: normalColor,
|
||||
backgroundColor: "rgba(0, 166, 89, .2)",
|
||||
pointBackgroundColor: pointColors(normalColor),
|
||||
fill: true,
|
||||
}],
|
||||
},
|
||||
options: { legend: { display: true }, scales: commonScales, maintainAspectRatio: false, responsive: true },
|
||||
});
|
||||
|
||||
destroyIfExists('rss');
|
||||
resourceHistoryChartInstances.rss = new Chart("ResourceRssChart", {
|
||||
type: "line",
|
||||
data: {
|
||||
labels: labels,
|
||||
datasets: [{
|
||||
label: getString("Systeminfo_Performance_Memory"),
|
||||
data: rssData,
|
||||
borderColor: "#3c8dbc",
|
||||
backgroundColor: "rgba(60, 141, 188, .2)",
|
||||
pointBackgroundColor: pointColors("#3c8dbc"),
|
||||
fill: true,
|
||||
}],
|
||||
},
|
||||
options: { legend: { display: true }, scales: commonScales, maintainAspectRatio: false, responsive: true },
|
||||
});
|
||||
|
||||
destroyIfExists('io');
|
||||
resourceHistoryChartInstances.io = new Chart("ResourceIoChart", {
|
||||
type: "line",
|
||||
data: {
|
||||
labels: labels,
|
||||
datasets: [
|
||||
{
|
||||
label: getString("Systeminfo_Performance_IO_Read"),
|
||||
data: ioReadData,
|
||||
borderColor: "#00a65a",
|
||||
pointBackgroundColor: pointColors("#00a65a"),
|
||||
fill: false,
|
||||
},
|
||||
{
|
||||
label: getString("Systeminfo_Performance_IO_Write"),
|
||||
data: ioWriteData,
|
||||
borderColor: "#f39c12",
|
||||
pointBackgroundColor: pointColors("#f39c12"),
|
||||
fill: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
options: { legend: { display: true }, scales: commonScales, maintainAspectRatio: false, responsive: true },
|
||||
});
|
||||
|
||||
destroyIfExists('duration');
|
||||
resourceHistoryChartInstances.duration = new Chart("ResourceDurationChart", {
|
||||
type: "line",
|
||||
data: {
|
||||
labels: labels,
|
||||
datasets: [{
|
||||
label: getString("Systeminfo_Performance_Duration"),
|
||||
data: durationData,
|
||||
borderColor: "#b2b6be",
|
||||
backgroundColor: "rgba(178, 182, 190, .2)",
|
||||
pointBackgroundColor: pointColors("#b2b6be"),
|
||||
fill: true,
|
||||
}],
|
||||
},
|
||||
options: { legend: { display: true }, scales: commonScales, maintainAspectRatio: false, responsive: true },
|
||||
});
|
||||
}
|
||||
@@ -12,43 +12,6 @@
|
||||
showSpinner();
|
||||
</script>
|
||||
|
||||
<?php
|
||||
|
||||
// Size and last mod of DB ------------------------------------------------------
|
||||
|
||||
$dbBasePath = rtrim(getenv('NETALERTX_DB') ?: '/data/db', '/');
|
||||
$nax_db = $dbBasePath . '/app.db';
|
||||
$nax_wal = $dbBasePath . '/app.db-wal';
|
||||
$nax_db_size = file_exists($nax_db) ? number_format((filesize($nax_db) / 1000000),2,",",".") . ' MB' : '0 MB';
|
||||
$nax_wal_size = file_exists($nax_wal) ? number_format((filesize($nax_wal) / 1000000),2,",",".") . ' MB' : '0 MB';
|
||||
$nax_db_mod = file_exists($nax_db) ? date ("F d Y H:i:s", filemtime($nax_db)) : 'N/A';
|
||||
|
||||
|
||||
// Table sizes -----------------------------------------------------------------
|
||||
|
||||
$tableSizesHTML = "";
|
||||
|
||||
// Open a connection to the SQLite database
|
||||
$db = new SQLite3($nax_db);
|
||||
|
||||
// Retrieve the table names from sqlite_master
|
||||
$query = "SELECT name FROM sqlite_master WHERE type='table'";
|
||||
$result = $db->query($query);
|
||||
|
||||
// Iterate over the tables and get the row counts
|
||||
while ($row = $result->fetchArray(SQLITE3_ASSOC)) {
|
||||
$tableName = $row['name'];
|
||||
$query = "SELECT COUNT(*) FROM $tableName";
|
||||
$countResult = $db->querySingle($query);
|
||||
$tableSizesHTML = $tableSizesHTML . "$tableName (<b>$countResult</b>), ";
|
||||
}
|
||||
|
||||
// Close the database connection
|
||||
$db->close();
|
||||
|
||||
|
||||
?>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="box" id="Maintain-Status">
|
||||
@@ -85,30 +48,6 @@ $db->close();
|
||||
<span data-plc="version"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="db_info_table_row">
|
||||
<div class="db_info_table_cell" style="min-width: 140px"><?= lang('Maintenance_database_path');?></div>
|
||||
<div class="db_info_table_cell">
|
||||
<?php echo $nax_db;?>
|
||||
</div>
|
||||
</div>
|
||||
<div class="db_info_table_row">
|
||||
<div class="db_info_table_cell"><?= lang('Maintenance_database_size');?></div>
|
||||
<div class="db_info_table_cell">
|
||||
<?php echo $nax_db_size;?> (wal: <?php echo $nax_wal_size;?>)
|
||||
</div>
|
||||
</div>
|
||||
<div class="db_info_table_row">
|
||||
<div class="db_info_table_cell"><?= lang('Maintenance_database_rows');?></div>
|
||||
<div class="db_info_table_cell">
|
||||
<?php echo $tableSizesHTML;?>
|
||||
</div>
|
||||
</div>
|
||||
<div class="db_info_table_row">
|
||||
<div class="db_info_table_cell"><?= lang('Maintenance_database_lastmod');?></div>
|
||||
<div class="db_info_table_cell">
|
||||
<?php echo $nax_db_mod;?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- /.box-body -->
|
||||
|
||||
@@ -474,6 +474,9 @@
|
||||
<li>
|
||||
<a href="systeminfo.php#panInitCheck" onclick="setCache('activeSysinfoTab','tabInitCheck');initializeTabs()"> <?= lang("Maintenance_InitCheck");?> </a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="systeminfo.php#panPerformance" onclick="setCache('activeSysinfoTab','tabPerformance');initializeTabs()"><?= lang('Systeminfo_Performance');?></a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
|
||||
@@ -341,6 +341,7 @@
|
||||
"Gen_Copy": "نسخ",
|
||||
"Gen_CopyToClipboard": "انسخ إلى الحافظة",
|
||||
"Gen_DataUpdatedUITakesTime": "تم تحديث البيانات. قد يستغرق تحديث واجهة المستخدم بعض الوقت",
|
||||
"Gen_Day": "",
|
||||
"Gen_Delete": "حذف",
|
||||
"Gen_DeleteAll": "حذف الكل",
|
||||
"Gen_Description": "الوصف",
|
||||
@@ -349,9 +350,11 @@
|
||||
"Gen_Filter": "تصفية",
|
||||
"Gen_Flapping": "",
|
||||
"Gen_Generate": "إنشاء",
|
||||
"Gen_Hour": "",
|
||||
"Gen_InvalidMac": "عنوان MAC غير صالح.",
|
||||
"Gen_Invalid_Value": "تم إدخال قيمة غير صالحة",
|
||||
"Gen_LockedDB": "قاعدة البيانات مقفلة",
|
||||
"Gen_Month": "",
|
||||
"Gen_NetworkMask": "قناع الشبكة",
|
||||
"Gen_New": "",
|
||||
"Gen_Next": "",
|
||||
@@ -383,6 +386,7 @@
|
||||
"Gen_Update_Value": "تحديث القيمة",
|
||||
"Gen_ValidIcon": "أيقونة صالحة",
|
||||
"Gen_Warning": "تحذير",
|
||||
"Gen_Week": "",
|
||||
"Gen_Work_In_Progress": "جاري العمل...",
|
||||
"Gen_create_new_device": "إنشاء جهاز جديد",
|
||||
"Gen_create_new_device_info": "معلومات إنشاء جهاز جديد",
|
||||
@@ -712,6 +716,14 @@
|
||||
"Systeminfo_Network_Server_Query": "استعلام الخادم",
|
||||
"Systeminfo_Network_Server_Query_String": "نص استعلام الخادم",
|
||||
"Systeminfo_Network_Server_Version": "إصدار الخادم",
|
||||
"Systeminfo_Performance": "",
|
||||
"Systeminfo_Performance_CPU": "",
|
||||
"Systeminfo_Performance_Disabled": "",
|
||||
"Systeminfo_Performance_Duration": "",
|
||||
"Systeminfo_Performance_IO_Caption": "",
|
||||
"Systeminfo_Performance_IO_Read": "",
|
||||
"Systeminfo_Performance_IO_Write": "",
|
||||
"Systeminfo_Performance_Memory": "",
|
||||
"Systeminfo_Services": "الخدمات",
|
||||
"Systeminfo_Services_Description": "وصف الخدمات",
|
||||
"Systeminfo_Services_Name": "اسم الخدمة",
|
||||
|
||||
@@ -341,6 +341,7 @@
|
||||
"Gen_Copy": "Executar",
|
||||
"Gen_CopyToClipboard": "Copia a portapapers",
|
||||
"Gen_DataUpdatedUITakesTime": "D'acord - Pot passar una estona perquè la interfície d'usuari s'actualitzi si s'està executant una exploració.",
|
||||
"Gen_Day": "",
|
||||
"Gen_Delete": "Esborrar",
|
||||
"Gen_DeleteAll": "Esborrar tot",
|
||||
"Gen_Description": "Descripció",
|
||||
@@ -349,9 +350,11 @@
|
||||
"Gen_Filter": "Filtrar",
|
||||
"Gen_Flapping": "Flapping",
|
||||
"Gen_Generate": "Generar",
|
||||
"Gen_Hour": "",
|
||||
"Gen_InvalidMac": "Mac address invàlida.",
|
||||
"Gen_Invalid_Value": "S'ha introduït un valor incorrecte",
|
||||
"Gen_LockedDB": "ERROR - DB podria estar bloquejada - Fes servir F12 Eines desenvolupament -> Consola o provar-ho més tard.",
|
||||
"Gen_Month": "",
|
||||
"Gen_NetworkMask": "Màscara de xarxa",
|
||||
"Gen_New": "Nou",
|
||||
"Gen_Next": "",
|
||||
@@ -383,6 +386,7 @@
|
||||
"Gen_Update_Value": "Actualitzar Valor",
|
||||
"Gen_ValidIcon": "<i class=\"fa-solid fa-chevron-right \"></i>",
|
||||
"Gen_Warning": "Advertència",
|
||||
"Gen_Week": "",
|
||||
"Gen_Work_In_Progress": "Work in progress, un bon moment per retroalimentació a https://github.com/netalertx/NetAlertX/issues",
|
||||
"Gen_create_new_device": "Nou dispositiu",
|
||||
"Gen_create_new_device_info": "Els dispositius són típicament descobert utilitzant <a target=\"_blank\" href=\"https://docs.netalertx.com/PLUGINS\">plugins</a>. Tanmateix, en certs casos, pots necessitar afegir dispositius a mà. Per explorar els temes concrets comproveu la <a target=\"_blank\" href=\"https://docs.netalertx.com/REMOTE_NETWORKS\">documentació de Xarxes Remotes</a>.",
|
||||
@@ -712,6 +716,14 @@
|
||||
"Systeminfo_Network_Server_Query": "Consulta del servidor:",
|
||||
"Systeminfo_Network_Server_Query_String": "Cap cadena de consulta",
|
||||
"Systeminfo_Network_Server_Version": "Versió del servidor:",
|
||||
"Systeminfo_Performance": "",
|
||||
"Systeminfo_Performance_CPU": "",
|
||||
"Systeminfo_Performance_Disabled": "",
|
||||
"Systeminfo_Performance_Duration": "",
|
||||
"Systeminfo_Performance_IO_Caption": "",
|
||||
"Systeminfo_Performance_IO_Read": "",
|
||||
"Systeminfo_Performance_IO_Write": "",
|
||||
"Systeminfo_Performance_Memory": "",
|
||||
"Systeminfo_Services": "Serveis",
|
||||
"Systeminfo_Services_Description": "Descripció del servei",
|
||||
"Systeminfo_Services_Name": "Nom del servei",
|
||||
|
||||
@@ -341,6 +341,7 @@
|
||||
"Gen_Copy": "Spustit",
|
||||
"Gen_CopyToClipboard": "Zkopírovat do schránky",
|
||||
"Gen_DataUpdatedUITakesTime": "OK – může zabrat chvíli aktualizovat rozhraní, pokud probíhá sken.",
|
||||
"Gen_Day": "",
|
||||
"Gen_Delete": "Smazat",
|
||||
"Gen_DeleteAll": "Smazat vše",
|
||||
"Gen_Description": "Popis",
|
||||
@@ -349,9 +350,11 @@
|
||||
"Gen_Filter": "Filtr",
|
||||
"Gen_Flapping": "Nestabilní",
|
||||
"Gen_Generate": "Nechat vytvořit",
|
||||
"Gen_Hour": "",
|
||||
"Gen_InvalidMac": "Neplatná MAC adresa.",
|
||||
"Gen_Invalid_Value": "Zadána neplatná hodnota",
|
||||
"Gen_LockedDB": "CHYBA – databáze je možná zamčená – Podívejte se do F12 -> Nástroje pro vývojáře -> Konzole. Nebo to zkuste později.",
|
||||
"Gen_Month": "",
|
||||
"Gen_NetworkMask": "Maska sítě",
|
||||
"Gen_New": "Nový",
|
||||
"Gen_Next": "Další",
|
||||
@@ -383,6 +386,7 @@
|
||||
"Gen_Update_Value": "Aktualizovat hodnotu",
|
||||
"Gen_ValidIcon": "<i class=\"fa-solid fa-chevron-right \"></i>",
|
||||
"Gen_Warning": "Varování",
|
||||
"Gen_Week": "",
|
||||
"Gen_Work_In_Progress": "Rozpracováno, ideální čas pro zpětnou vazbu na https://github.com/netalertx/NetAlertX/issues",
|
||||
"Gen_create_new_device": "Nové zařízení",
|
||||
"Gen_create_new_device_info": "Zařízení jsou vyhledávána pomocí <a target=\"_blank\" href=\"https://docs.netalertx.com/PLUGINS\">zásuvných modulů</a>. V určitých případech je však potřeba přidat zařízení ručně. Pokud chcete prozkoumat konkrétní scénáře, podívejte se do <a target=\"_blank\" href=\"https://docs.netalertx.com/REMOTE_NETWORKS\">Dokumentace ke vzdáleným sítím</a>.",
|
||||
@@ -712,6 +716,14 @@
|
||||
"Systeminfo_Network_Server_Query": "Dotaz serveru:",
|
||||
"Systeminfo_Network_Server_Query_String": "Žádný řetězec dotazu",
|
||||
"Systeminfo_Network_Server_Version": "Verze serveru:",
|
||||
"Systeminfo_Performance": "",
|
||||
"Systeminfo_Performance_CPU": "",
|
||||
"Systeminfo_Performance_Disabled": "",
|
||||
"Systeminfo_Performance_Duration": "",
|
||||
"Systeminfo_Performance_IO_Caption": "",
|
||||
"Systeminfo_Performance_IO_Read": "",
|
||||
"Systeminfo_Performance_IO_Write": "",
|
||||
"Systeminfo_Performance_Memory": "",
|
||||
"Systeminfo_Services": "Služby",
|
||||
"Systeminfo_Services_Description": "Popis služby",
|
||||
"Systeminfo_Services_Name": "Název služby",
|
||||
@@ -840,4 +852,4 @@
|
||||
"settings_system_label": "Systém",
|
||||
"settings_update_item_warning": "Zaktualizujte níže uvedenou hodnotu. Dávejte pozor, ať je dodržen předchozí formát. <b>Ověřování správnosti není prováděno.</b>",
|
||||
"test_event_tooltip": "Než budete svá nastavení zkoušet, nejprve vámi provedené změny uložte."
|
||||
}
|
||||
}
|
||||
@@ -345,6 +345,7 @@
|
||||
"Gen_Copy": "Ausführen",
|
||||
"Gen_CopyToClipboard": "In die Zwischenablage kopieren",
|
||||
"Gen_DataUpdatedUITakesTime": "OK – Es kann einen Moment dauern, bis die Benutzeroberfläche aktualisiert wird, während ein Scan ausgeführt wird.",
|
||||
"Gen_Day": "",
|
||||
"Gen_Delete": "Löschen",
|
||||
"Gen_DeleteAll": "Alles löschen",
|
||||
"Gen_Description": "Beschreibung",
|
||||
@@ -353,9 +354,11 @@
|
||||
"Gen_Filter": "Filter",
|
||||
"Gen_Flapping": "",
|
||||
"Gen_Generate": "Generieren",
|
||||
"Gen_Hour": "",
|
||||
"Gen_InvalidMac": "Ungültige MAC-Adresse.",
|
||||
"Gen_Invalid_Value": "Ein ungültiger Wert wurde eingegeben",
|
||||
"Gen_LockedDB": "ERROR - DB eventuell gesperrt - Nutze die Konsole in den Entwickler Werkzeugen (F12) zur Überprüfung oder probiere es später erneut.",
|
||||
"Gen_Month": "",
|
||||
"Gen_NetworkMask": "Netzmaske",
|
||||
"Gen_New": "Neu",
|
||||
"Gen_Next": "",
|
||||
@@ -387,6 +390,7 @@
|
||||
"Gen_Update_Value": "Wert aktualisieren",
|
||||
"Gen_ValidIcon": "<i class=\"fa-solid fa-chevron-right \"></i>",
|
||||
"Gen_Warning": "Warnung",
|
||||
"Gen_Week": "",
|
||||
"Gen_Work_In_Progress": "Keine Finalversion, feedback bitte unter: https://github.com/netalertx/NetAlertX/issues",
|
||||
"Gen_create_new_device": "Neues Gerät",
|
||||
"Gen_create_new_device_info": "Geräte werden normalerweise über <a target=\"_blank\" href=\"https://docs.netalertx.com/PLUGINS\">Plugins</a> gefunden. In Ausnahmefällen kann es nötig sein, sie manuell hinzuzufügen. Konkrete Szenarien sind in der <a target=\"_blank\" href=\"https://docs.netalertx.com/REMOTE_NETWORKS\">Dokumentation über entfernte Netzwerke</a> zu finden.",
|
||||
@@ -773,6 +777,14 @@
|
||||
"Systeminfo_Network_Server_Query": "Serverabfrage:",
|
||||
"Systeminfo_Network_Server_Query_String": "Keine Abfragezeichenfolge",
|
||||
"Systeminfo_Network_Server_Version": "Serverversion:",
|
||||
"Systeminfo_Performance": "",
|
||||
"Systeminfo_Performance_CPU": "",
|
||||
"Systeminfo_Performance_Disabled": "",
|
||||
"Systeminfo_Performance_Duration": "",
|
||||
"Systeminfo_Performance_IO_Caption": "",
|
||||
"Systeminfo_Performance_IO_Read": "",
|
||||
"Systeminfo_Performance_IO_Write": "",
|
||||
"Systeminfo_Performance_Memory": "",
|
||||
"Systeminfo_Services": "Dienste",
|
||||
"Systeminfo_Services_Description": "Dienstbeschreibung",
|
||||
"Systeminfo_Services_Name": "Dienstname",
|
||||
|
||||
@@ -341,6 +341,7 @@
|
||||
"Gen_Copy": "Run",
|
||||
"Gen_CopyToClipboard": "Copy to clipboard",
|
||||
"Gen_DataUpdatedUITakesTime": "OK - It may take a while for the UI to update if a scan is running.",
|
||||
"Gen_Day": "Day",
|
||||
"Gen_Delete": "Delete",
|
||||
"Gen_DeleteAll": "Delete all",
|
||||
"Gen_Description": "Description",
|
||||
@@ -349,9 +350,11 @@
|
||||
"Gen_Filter": "Filter",
|
||||
"Gen_Flapping": "Flapping",
|
||||
"Gen_Generate": "Generate",
|
||||
"Gen_Hour": "Hour",
|
||||
"Gen_InvalidMac": "Invalid Mac address.",
|
||||
"Gen_Invalid_Value": "An invalid value was entered",
|
||||
"Gen_LockedDB": "ERROR - DB might be locked - Check F12 Dev tools -> Console or try later.",
|
||||
"Gen_Month": "Month",
|
||||
"Gen_NetworkMask": "Network mask",
|
||||
"Gen_New": "New",
|
||||
"Gen_Next": "Next",
|
||||
@@ -383,6 +386,7 @@
|
||||
"Gen_Update_Value": "Update Value",
|
||||
"Gen_ValidIcon": "<i class=\"fa-solid fa-chevron-right \"></i>",
|
||||
"Gen_Warning": "Warning",
|
||||
"Gen_Week": "Week",
|
||||
"Gen_Work_In_Progress": "Work in progress, good time to feedback on https://github.com/netalertx/NetAlertX/issues",
|
||||
"Gen_create_new_device": "New device",
|
||||
"Gen_create_new_device_info": "Devices are typically discovered using <a target=\"_blank\" href=\"https://docs.netalertx.com/PLUGINS\">plugins</a>. However, in certain cases, you may need to add devices manually. To explore specific scenarios check the <a target=\"_blank\" href=\"https://docs.netalertx.com/REMOTE_NETWORKS\">Remote Networks documentation</a>.",
|
||||
@@ -712,6 +716,14 @@
|
||||
"Systeminfo_Network_Server_Query": "Server query:",
|
||||
"Systeminfo_Network_Server_Query_String": "No query string",
|
||||
"Systeminfo_Network_Server_Version": "Server Version:",
|
||||
"Systeminfo_Performance": "Performance",
|
||||
"Systeminfo_Performance_CPU": "CPU %",
|
||||
"Systeminfo_Performance_Disabled": "Resource history collection is currently disabled. <a onclick=\"toggleAllSettings()\" href=\"#MAINT_PERF_DAYS\">Enable it in Settings</a>.",
|
||||
"Systeminfo_Performance_Duration": "Scan Duration (s)",
|
||||
"Systeminfo_Performance_IO_Caption": "Direct NetAlertX process I/O only - plugin subprocess I/O (e.g. a scanner writing its own result file) is not included.",
|
||||
"Systeminfo_Performance_IO_Read": "IO Read (MB)",
|
||||
"Systeminfo_Performance_IO_Write": "IO Write (MB)",
|
||||
"Systeminfo_Performance_Memory": "Memory Usage (MB)",
|
||||
"Systeminfo_Services": "Services",
|
||||
"Systeminfo_Services_Description": "Service Description",
|
||||
"Systeminfo_Services_Name": "Service Name",
|
||||
|
||||
@@ -343,6 +343,7 @@
|
||||
"Gen_Copy": "Ejecutar",
|
||||
"Gen_CopyToClipboard": "Copiar al portapapeles",
|
||||
"Gen_DataUpdatedUITakesTime": "Correcto - La interfaz puede tardar en actualizarse si se está ejecutando un escaneo.",
|
||||
"Gen_Day": "",
|
||||
"Gen_Delete": "Eliminar",
|
||||
"Gen_DeleteAll": "Eliminar todo",
|
||||
"Gen_Description": "Descripción",
|
||||
@@ -351,9 +352,11 @@
|
||||
"Gen_Filter": "Filtro",
|
||||
"Gen_Flapping": "Agitado",
|
||||
"Gen_Generate": "Generar",
|
||||
"Gen_Hour": "",
|
||||
"Gen_InvalidMac": "Dirección MAC inválida.",
|
||||
"Gen_Invalid_Value": "Un valor inválido fue ingresado",
|
||||
"Gen_LockedDB": "Fallo - La base de datos puede estar bloqueada - Pulsa F1 -> Ajustes de desarrolladores -> Consola o prueba más tarde.",
|
||||
"Gen_Month": "",
|
||||
"Gen_NetworkMask": "Máscara de red",
|
||||
"Gen_New": "Nuevo",
|
||||
"Gen_Next": "",
|
||||
@@ -385,6 +388,7 @@
|
||||
"Gen_Update_Value": "Actualizar valor",
|
||||
"Gen_ValidIcon": "<i class=\"fa-solid fa-chevron-right \"></i>",
|
||||
"Gen_Warning": "Advertencia",
|
||||
"Gen_Week": "",
|
||||
"Gen_Work_In_Progress": "Trabajo en curso, un buen momento para hacer comentarios en https://github.com/netalertx/NetAlertX/issues",
|
||||
"Gen_create_new_device": "Nuevo dispositivo",
|
||||
"Gen_create_new_device_info": "Los dispositivos se suelen descubrir utilizando <a target=\"_blank\" href=\"https://docs.netalertx.com/PLUGINS\">plugins</a>. Sin embargo, en algunos casos, es posible que necesite agregar dispositivos manualmente. Para explorar escenarios específicos, consulte la documentación <a target=\"_blank\" href=\"https://docs.netalertx.com/REMOTE_NETWORKS\">Redes remotas</a>.",
|
||||
@@ -772,6 +776,14 @@
|
||||
"Systeminfo_Network_Server_Query": "Consulta del servidor:",
|
||||
"Systeminfo_Network_Server_Query_String": "Sin cadena de consulta",
|
||||
"Systeminfo_Network_Server_Version": "Versión del servidor:",
|
||||
"Systeminfo_Performance": "",
|
||||
"Systeminfo_Performance_CPU": "",
|
||||
"Systeminfo_Performance_Disabled": "",
|
||||
"Systeminfo_Performance_Duration": "",
|
||||
"Systeminfo_Performance_IO_Caption": "",
|
||||
"Systeminfo_Performance_IO_Read": "",
|
||||
"Systeminfo_Performance_IO_Write": "",
|
||||
"Systeminfo_Performance_Memory": "",
|
||||
"Systeminfo_Services": "Servicios",
|
||||
"Systeminfo_Services_Description": "Descripción del servicio",
|
||||
"Systeminfo_Services_Name": "Nombre del servicio",
|
||||
|
||||
@@ -341,6 +341,7 @@
|
||||
"Gen_Copy": "",
|
||||
"Gen_CopyToClipboard": "",
|
||||
"Gen_DataUpdatedUITakesTime": "",
|
||||
"Gen_Day": "",
|
||||
"Gen_Delete": "",
|
||||
"Gen_DeleteAll": "",
|
||||
"Gen_Description": "",
|
||||
@@ -349,9 +350,11 @@
|
||||
"Gen_Filter": "",
|
||||
"Gen_Flapping": "",
|
||||
"Gen_Generate": "",
|
||||
"Gen_Hour": "",
|
||||
"Gen_InvalidMac": "",
|
||||
"Gen_Invalid_Value": "",
|
||||
"Gen_LockedDB": "",
|
||||
"Gen_Month": "",
|
||||
"Gen_NetworkMask": "",
|
||||
"Gen_New": "",
|
||||
"Gen_Next": "",
|
||||
@@ -383,6 +386,7 @@
|
||||
"Gen_Update_Value": "",
|
||||
"Gen_ValidIcon": "",
|
||||
"Gen_Warning": "",
|
||||
"Gen_Week": "",
|
||||
"Gen_Work_In_Progress": "",
|
||||
"Gen_create_new_device": "",
|
||||
"Gen_create_new_device_info": "",
|
||||
@@ -712,6 +716,14 @@
|
||||
"Systeminfo_Network_Server_Query": "",
|
||||
"Systeminfo_Network_Server_Query_String": "",
|
||||
"Systeminfo_Network_Server_Version": "",
|
||||
"Systeminfo_Performance": "",
|
||||
"Systeminfo_Performance_CPU": "",
|
||||
"Systeminfo_Performance_Disabled": "",
|
||||
"Systeminfo_Performance_Duration": "",
|
||||
"Systeminfo_Performance_IO_Caption": "",
|
||||
"Systeminfo_Performance_IO_Read": "",
|
||||
"Systeminfo_Performance_IO_Write": "",
|
||||
"Systeminfo_Performance_Memory": "",
|
||||
"Systeminfo_Services": "",
|
||||
"Systeminfo_Services_Description": "",
|
||||
"Systeminfo_Services_Name": "",
|
||||
|
||||
@@ -341,6 +341,7 @@
|
||||
"Gen_Copy": "",
|
||||
"Gen_CopyToClipboard": "",
|
||||
"Gen_DataUpdatedUITakesTime": "",
|
||||
"Gen_Day": "",
|
||||
"Gen_Delete": "",
|
||||
"Gen_DeleteAll": "",
|
||||
"Gen_Description": "",
|
||||
@@ -349,9 +350,11 @@
|
||||
"Gen_Filter": "",
|
||||
"Gen_Flapping": "",
|
||||
"Gen_Generate": "",
|
||||
"Gen_Hour": "",
|
||||
"Gen_InvalidMac": "",
|
||||
"Gen_Invalid_Value": "",
|
||||
"Gen_LockedDB": "",
|
||||
"Gen_Month": "",
|
||||
"Gen_NetworkMask": "",
|
||||
"Gen_New": "",
|
||||
"Gen_Next": "",
|
||||
@@ -383,6 +386,7 @@
|
||||
"Gen_Update_Value": "",
|
||||
"Gen_ValidIcon": "",
|
||||
"Gen_Warning": "",
|
||||
"Gen_Week": "",
|
||||
"Gen_Work_In_Progress": "",
|
||||
"Gen_create_new_device": "",
|
||||
"Gen_create_new_device_info": "",
|
||||
@@ -712,6 +716,14 @@
|
||||
"Systeminfo_Network_Server_Query": "",
|
||||
"Systeminfo_Network_Server_Query_String": "",
|
||||
"Systeminfo_Network_Server_Version": "",
|
||||
"Systeminfo_Performance": "",
|
||||
"Systeminfo_Performance_CPU": "",
|
||||
"Systeminfo_Performance_Disabled": "",
|
||||
"Systeminfo_Performance_Duration": "",
|
||||
"Systeminfo_Performance_IO_Caption": "",
|
||||
"Systeminfo_Performance_IO_Read": "",
|
||||
"Systeminfo_Performance_IO_Write": "",
|
||||
"Systeminfo_Performance_Memory": "",
|
||||
"Systeminfo_Services": "",
|
||||
"Systeminfo_Services_Description": "",
|
||||
"Systeminfo_Services_Name": "",
|
||||
|
||||
@@ -341,6 +341,7 @@
|
||||
"Gen_Copy": "Lancer",
|
||||
"Gen_CopyToClipboard": "Copier vers le presse-papier",
|
||||
"Gen_DataUpdatedUITakesTime": "OK - cela peut prendre du temps à l'interface pour se mettre à jour si un scan est en cours.",
|
||||
"Gen_Day": "",
|
||||
"Gen_Delete": "Supprimer",
|
||||
"Gen_DeleteAll": "Supprimer tous",
|
||||
"Gen_Description": "Description",
|
||||
@@ -349,9 +350,11 @@
|
||||
"Gen_Filter": "Filtrer",
|
||||
"Gen_Flapping": "Bagotage",
|
||||
"Gen_Generate": "Générer",
|
||||
"Gen_Hour": "",
|
||||
"Gen_InvalidMac": "Adresse MAC invalide.",
|
||||
"Gen_Invalid_Value": "Une valeur invalide a été renseignée",
|
||||
"Gen_LockedDB": "Erreur - La base de données est peut-être verrouillée - Vérifier avec les outils de dév via F12 -> Console ou essayer plus tard.",
|
||||
"Gen_Month": "",
|
||||
"Gen_NetworkMask": "Masque réseau",
|
||||
"Gen_New": "Nouveau",
|
||||
"Gen_Next": "Suivant",
|
||||
@@ -383,6 +386,7 @@
|
||||
"Gen_Update_Value": "Valeur à mettre à jour",
|
||||
"Gen_ValidIcon": "<i class=\"fa-solid fa-chevron-right \"></i>",
|
||||
"Gen_Warning": "Avertissement",
|
||||
"Gen_Week": "",
|
||||
"Gen_Work_In_Progress": "Travaux en cours, c'est le bon moment pour faire un retour via la liste d'anomalies sur Github https://github.com/netalertx/NetAlertX/issues",
|
||||
"Gen_create_new_device": "Nouvel appareil",
|
||||
"Gen_create_new_device_info": "Les appareils sont souvent découverts à l'aide d'un <a target=\"_blank\" href=\"https://docs.netalertx.com/PLUGINS\">plugin</a>. Cependant, dans certains cas, vous pouvez ajouter manuellement les appareils. Pour explorer des scénarios spécifiques, consulter la <a target=\"_blank\" href=\"https://docs.netalertx.com/REMOTE_NETWORKS\">documentation des réseaux distants</a>.",
|
||||
@@ -712,6 +716,14 @@
|
||||
"Systeminfo_Network_Server_Query": "Requête du serveur :",
|
||||
"Systeminfo_Network_Server_Query_String": "Aucune chaîne de requête",
|
||||
"Systeminfo_Network_Server_Version": "Version du serveur :",
|
||||
"Systeminfo_Performance": "",
|
||||
"Systeminfo_Performance_CPU": "",
|
||||
"Systeminfo_Performance_Disabled": "",
|
||||
"Systeminfo_Performance_Duration": "",
|
||||
"Systeminfo_Performance_IO_Caption": "",
|
||||
"Systeminfo_Performance_IO_Read": "",
|
||||
"Systeminfo_Performance_IO_Write": "",
|
||||
"Systeminfo_Performance_Memory": "",
|
||||
"Systeminfo_Services": "Services",
|
||||
"Systeminfo_Services_Description": "Description du service",
|
||||
"Systeminfo_Services_Name": "Nom du service",
|
||||
|
||||
@@ -341,6 +341,7 @@
|
||||
"Gen_Copy": "",
|
||||
"Gen_CopyToClipboard": "",
|
||||
"Gen_DataUpdatedUITakesTime": "",
|
||||
"Gen_Day": "",
|
||||
"Gen_Delete": "",
|
||||
"Gen_DeleteAll": "",
|
||||
"Gen_Description": "",
|
||||
@@ -349,9 +350,11 @@
|
||||
"Gen_Filter": "",
|
||||
"Gen_Flapping": "",
|
||||
"Gen_Generate": "",
|
||||
"Gen_Hour": "",
|
||||
"Gen_InvalidMac": "",
|
||||
"Gen_Invalid_Value": "",
|
||||
"Gen_LockedDB": "",
|
||||
"Gen_Month": "",
|
||||
"Gen_NetworkMask": "",
|
||||
"Gen_New": "",
|
||||
"Gen_Next": "",
|
||||
@@ -383,6 +386,7 @@
|
||||
"Gen_Update_Value": "",
|
||||
"Gen_ValidIcon": "",
|
||||
"Gen_Warning": "",
|
||||
"Gen_Week": "",
|
||||
"Gen_Work_In_Progress": "",
|
||||
"Gen_create_new_device": "",
|
||||
"Gen_create_new_device_info": "",
|
||||
@@ -712,6 +716,14 @@
|
||||
"Systeminfo_Network_Server_Query": "",
|
||||
"Systeminfo_Network_Server_Query_String": "",
|
||||
"Systeminfo_Network_Server_Version": "",
|
||||
"Systeminfo_Performance": "",
|
||||
"Systeminfo_Performance_CPU": "",
|
||||
"Systeminfo_Performance_Disabled": "",
|
||||
"Systeminfo_Performance_Duration": "",
|
||||
"Systeminfo_Performance_IO_Caption": "",
|
||||
"Systeminfo_Performance_IO_Read": "",
|
||||
"Systeminfo_Performance_IO_Write": "",
|
||||
"Systeminfo_Performance_Memory": "",
|
||||
"Systeminfo_Services": "",
|
||||
"Systeminfo_Services_Description": "",
|
||||
"Systeminfo_Services_Name": "",
|
||||
|
||||
@@ -341,6 +341,7 @@
|
||||
"Gen_Copy": "",
|
||||
"Gen_CopyToClipboard": "",
|
||||
"Gen_DataUpdatedUITakesTime": "",
|
||||
"Gen_Day": "",
|
||||
"Gen_Delete": "",
|
||||
"Gen_DeleteAll": "",
|
||||
"Gen_Description": "",
|
||||
@@ -349,9 +350,11 @@
|
||||
"Gen_Filter": "",
|
||||
"Gen_Flapping": "",
|
||||
"Gen_Generate": "",
|
||||
"Gen_Hour": "",
|
||||
"Gen_InvalidMac": "",
|
||||
"Gen_Invalid_Value": "",
|
||||
"Gen_LockedDB": "",
|
||||
"Gen_Month": "",
|
||||
"Gen_NetworkMask": "",
|
||||
"Gen_New": "",
|
||||
"Gen_Next": "",
|
||||
@@ -383,6 +386,7 @@
|
||||
"Gen_Update_Value": "",
|
||||
"Gen_ValidIcon": "",
|
||||
"Gen_Warning": "",
|
||||
"Gen_Week": "",
|
||||
"Gen_Work_In_Progress": "",
|
||||
"Gen_create_new_device": "",
|
||||
"Gen_create_new_device_info": "",
|
||||
@@ -712,6 +716,14 @@
|
||||
"Systeminfo_Network_Server_Query": "",
|
||||
"Systeminfo_Network_Server_Query_String": "",
|
||||
"Systeminfo_Network_Server_Version": "",
|
||||
"Systeminfo_Performance": "",
|
||||
"Systeminfo_Performance_CPU": "",
|
||||
"Systeminfo_Performance_Disabled": "",
|
||||
"Systeminfo_Performance_Duration": "",
|
||||
"Systeminfo_Performance_IO_Caption": "",
|
||||
"Systeminfo_Performance_IO_Read": "",
|
||||
"Systeminfo_Performance_IO_Write": "",
|
||||
"Systeminfo_Performance_Memory": "",
|
||||
"Systeminfo_Services": "",
|
||||
"Systeminfo_Services_Description": "",
|
||||
"Systeminfo_Services_Name": "",
|
||||
|
||||
@@ -341,6 +341,7 @@
|
||||
"Gen_Copy": "",
|
||||
"Gen_CopyToClipboard": "",
|
||||
"Gen_DataUpdatedUITakesTime": "",
|
||||
"Gen_Day": "",
|
||||
"Gen_Delete": "",
|
||||
"Gen_DeleteAll": "",
|
||||
"Gen_Description": "",
|
||||
@@ -349,9 +350,11 @@
|
||||
"Gen_Filter": "",
|
||||
"Gen_Flapping": "",
|
||||
"Gen_Generate": "",
|
||||
"Gen_Hour": "",
|
||||
"Gen_InvalidMac": "",
|
||||
"Gen_Invalid_Value": "",
|
||||
"Gen_LockedDB": "",
|
||||
"Gen_Month": "",
|
||||
"Gen_NetworkMask": "",
|
||||
"Gen_New": "",
|
||||
"Gen_Next": "",
|
||||
@@ -383,6 +386,7 @@
|
||||
"Gen_Update_Value": "",
|
||||
"Gen_ValidIcon": "",
|
||||
"Gen_Warning": "",
|
||||
"Gen_Week": "",
|
||||
"Gen_Work_In_Progress": "",
|
||||
"Gen_create_new_device": "",
|
||||
"Gen_create_new_device_info": "",
|
||||
@@ -712,6 +716,14 @@
|
||||
"Systeminfo_Network_Server_Query": "",
|
||||
"Systeminfo_Network_Server_Query_String": "",
|
||||
"Systeminfo_Network_Server_Version": "",
|
||||
"Systeminfo_Performance": "",
|
||||
"Systeminfo_Performance_CPU": "",
|
||||
"Systeminfo_Performance_Disabled": "",
|
||||
"Systeminfo_Performance_Duration": "",
|
||||
"Systeminfo_Performance_IO_Caption": "",
|
||||
"Systeminfo_Performance_IO_Read": "",
|
||||
"Systeminfo_Performance_IO_Write": "",
|
||||
"Systeminfo_Performance_Memory": "",
|
||||
"Systeminfo_Services": "",
|
||||
"Systeminfo_Services_Description": "",
|
||||
"Systeminfo_Services_Name": "",
|
||||
|
||||
@@ -341,6 +341,7 @@
|
||||
"Gen_Copy": "Esegui",
|
||||
"Gen_CopyToClipboard": "Copia negli appunti",
|
||||
"Gen_DataUpdatedUITakesTime": "OK: l'aggiornamento dell'interfaccia utente potrebbe richiedere del tempo se è in esecuzione una scansione.",
|
||||
"Gen_Day": "",
|
||||
"Gen_Delete": "Elimina",
|
||||
"Gen_DeleteAll": "Elimina tutti",
|
||||
"Gen_Description": "Descrizione",
|
||||
@@ -349,9 +350,11 @@
|
||||
"Gen_Filter": "Filtro",
|
||||
"Gen_Flapping": "Flapping",
|
||||
"Gen_Generate": "Genera",
|
||||
"Gen_Hour": "",
|
||||
"Gen_InvalidMac": "Indirizzo Mac non valido.",
|
||||
"Gen_Invalid_Value": "È stato inserito un valore non valido",
|
||||
"Gen_LockedDB": "ERRORE: il DB potrebbe essere bloccato, controlla F12 Strumenti di sviluppo -> Console o riprova più tardi.",
|
||||
"Gen_Month": "",
|
||||
"Gen_NetworkMask": "Maschera di rete",
|
||||
"Gen_New": "Nuovo",
|
||||
"Gen_Next": "Successivo",
|
||||
@@ -383,6 +386,7 @@
|
||||
"Gen_Update_Value": "Aggiorna valore",
|
||||
"Gen_ValidIcon": "<i class=\"fa-solid fa-chevron-right \"></i>",
|
||||
"Gen_Warning": "Avviso",
|
||||
"Gen_Week": "",
|
||||
"Gen_Work_In_Progress": "Lavori in corso, è quindi un buon momento per un feedback su https://github.com/netalertx/NetAlertX/issues",
|
||||
"Gen_create_new_device": "Nuovo dispositivo",
|
||||
"Gen_create_new_device_info": "I dispositivi vengono generalmente rilevati utilizzando <a target=\"_blank\" href=\"https://docs.netalertx.com/PLUGINS\">plugin</a>. Tuttavia, in alcuni casi, potrebbe essere necessario aggiungere manualmente i dispositivi. Per esplorare scenari specifici, consulta la <a target=\"_blank\" href=\"https://docs.netalertx.com/REMOTE_NETWORKS\">documentazione sulle reti remote</a>.",
|
||||
@@ -712,6 +716,14 @@
|
||||
"Systeminfo_Network_Server_Query": "Interrogazione server:",
|
||||
"Systeminfo_Network_Server_Query_String": "Nessuna stringa di interrogazione",
|
||||
"Systeminfo_Network_Server_Version": "Versione server:",
|
||||
"Systeminfo_Performance": "",
|
||||
"Systeminfo_Performance_CPU": "",
|
||||
"Systeminfo_Performance_Disabled": "",
|
||||
"Systeminfo_Performance_Duration": "",
|
||||
"Systeminfo_Performance_IO_Caption": "",
|
||||
"Systeminfo_Performance_IO_Read": "",
|
||||
"Systeminfo_Performance_IO_Write": "",
|
||||
"Systeminfo_Performance_Memory": "",
|
||||
"Systeminfo_Services": "Servizi",
|
||||
"Systeminfo_Services_Description": "Descrizione servizio",
|
||||
"Systeminfo_Services_Name": "Nome servizio",
|
||||
|
||||
@@ -341,6 +341,7 @@
|
||||
"Gen_Copy": "実行",
|
||||
"Gen_CopyToClipboard": "クリップボードにコピー",
|
||||
"Gen_DataUpdatedUITakesTime": "OK - スキャン実行中は、UIの更新に時間がかかることがあります。",
|
||||
"Gen_Day": "",
|
||||
"Gen_Delete": "削除",
|
||||
"Gen_DeleteAll": "全削除",
|
||||
"Gen_Description": "説明",
|
||||
@@ -349,9 +350,11 @@
|
||||
"Gen_Filter": "フィルター",
|
||||
"Gen_Flapping": "フラッピング",
|
||||
"Gen_Generate": "生成",
|
||||
"Gen_Hour": "",
|
||||
"Gen_InvalidMac": "無効なMacアドレス。",
|
||||
"Gen_Invalid_Value": "無効な値が入力されました",
|
||||
"Gen_LockedDB": "エラー - DBがロックされている可能性があります - F12で開発者ツール→コンソールを確認するか、後で試してください。",
|
||||
"Gen_Month": "",
|
||||
"Gen_NetworkMask": "ネットワークマスク",
|
||||
"Gen_New": "New",
|
||||
"Gen_Next": "",
|
||||
@@ -383,6 +386,7 @@
|
||||
"Gen_Update_Value": "更新値",
|
||||
"Gen_ValidIcon": "<i class=\"fa-solid fa-chevron-right \"></i>",
|
||||
"Gen_Warning": "警告",
|
||||
"Gen_Week": "",
|
||||
"Gen_Work_In_Progress": "作業中、https://github.com/netalertx/NetAlertX/issues へのフィードバックの好機です",
|
||||
"Gen_create_new_device": "新規デバイス",
|
||||
"Gen_create_new_device_info": "デバイスは通常、<a target=\"_blank\" href=\"https://docs.netalertx.com/PLUGINS\">プラグイン</a> を使用して検出されます。ただし、特定のケースでは手動でデバイスを追加する必要がある場合があります。具体的なシナリオについては、<a target=\"_blank\" href=\"https://docs.netalertx.com/REMOTE_NETWORKS\">リモートネットワークドキュメント</a> を参照してください。",
|
||||
@@ -712,6 +716,14 @@
|
||||
"Systeminfo_Network_Server_Query": "サーバークエリ:",
|
||||
"Systeminfo_Network_Server_Query_String": "クエリ文字列なし",
|
||||
"Systeminfo_Network_Server_Version": "サーバーバージョン:",
|
||||
"Systeminfo_Performance": "",
|
||||
"Systeminfo_Performance_CPU": "",
|
||||
"Systeminfo_Performance_Disabled": "",
|
||||
"Systeminfo_Performance_Duration": "",
|
||||
"Systeminfo_Performance_IO_Caption": "",
|
||||
"Systeminfo_Performance_IO_Read": "",
|
||||
"Systeminfo_Performance_IO_Write": "",
|
||||
"Systeminfo_Performance_Memory": "",
|
||||
"Systeminfo_Services": "サービス",
|
||||
"Systeminfo_Services_Description": "サービス内容",
|
||||
"Systeminfo_Services_Name": "サービス名",
|
||||
|
||||
@@ -341,6 +341,7 @@
|
||||
"Gen_Copy": "Kjør",
|
||||
"Gen_CopyToClipboard": "",
|
||||
"Gen_DataUpdatedUITakesTime": "OK - Det kan ta litt tid før brukergrensesnittet oppdateres hvis en skanning kjøres.",
|
||||
"Gen_Day": "",
|
||||
"Gen_Delete": "Slett",
|
||||
"Gen_DeleteAll": "Slett alle",
|
||||
"Gen_Description": "",
|
||||
@@ -349,9 +350,11 @@
|
||||
"Gen_Filter": "Filter",
|
||||
"Gen_Flapping": "",
|
||||
"Gen_Generate": "",
|
||||
"Gen_Hour": "",
|
||||
"Gen_InvalidMac": "",
|
||||
"Gen_Invalid_Value": "",
|
||||
"Gen_LockedDB": "FEIL - DB kan være låst - Sjekk F12 Dev tools -> Konsoll eller prøv senere.",
|
||||
"Gen_Month": "",
|
||||
"Gen_NetworkMask": "",
|
||||
"Gen_New": "",
|
||||
"Gen_Next": "",
|
||||
@@ -383,6 +386,7 @@
|
||||
"Gen_Update_Value": "Oppdater verdi",
|
||||
"Gen_ValidIcon": "",
|
||||
"Gen_Warning": "Advarsel",
|
||||
"Gen_Week": "",
|
||||
"Gen_Work_In_Progress": "Work in progress, gjerne kom med tilbakemeldinger på https://github.com/netalertx/NetAlertX/issues",
|
||||
"Gen_create_new_device": "",
|
||||
"Gen_create_new_device_info": "",
|
||||
@@ -712,6 +716,14 @@
|
||||
"Systeminfo_Network_Server_Query": "Serverforespørsel:",
|
||||
"Systeminfo_Network_Server_Query_String": "Ingen spørringsstreng",
|
||||
"Systeminfo_Network_Server_Version": "Server versjon:",
|
||||
"Systeminfo_Performance": "",
|
||||
"Systeminfo_Performance_CPU": "",
|
||||
"Systeminfo_Performance_Disabled": "",
|
||||
"Systeminfo_Performance_Duration": "",
|
||||
"Systeminfo_Performance_IO_Caption": "",
|
||||
"Systeminfo_Performance_IO_Read": "",
|
||||
"Systeminfo_Performance_IO_Write": "",
|
||||
"Systeminfo_Performance_Memory": "",
|
||||
"Systeminfo_Services": "Tjenester",
|
||||
"Systeminfo_Services_Description": "Tjeneste beskrivelse",
|
||||
"Systeminfo_Services_Name": "Tjeneste navn",
|
||||
|
||||
@@ -341,6 +341,7 @@
|
||||
"Gen_Copy": "Wykonaj",
|
||||
"Gen_CopyToClipboard": "",
|
||||
"Gen_DataUpdatedUITakesTime": "OK – Może to potrwać chwilę, zanim interfejs użytkownika się zaktualizuje, jeśli trwa skan.",
|
||||
"Gen_Day": "",
|
||||
"Gen_Delete": "Usuń",
|
||||
"Gen_DeleteAll": "Usuń wszystko",
|
||||
"Gen_Description": "Opis",
|
||||
@@ -349,9 +350,11 @@
|
||||
"Gen_Filter": "Filtr",
|
||||
"Gen_Flapping": "",
|
||||
"Gen_Generate": "Wygeneruj",
|
||||
"Gen_Hour": "",
|
||||
"Gen_InvalidMac": "",
|
||||
"Gen_Invalid_Value": "",
|
||||
"Gen_LockedDB": "Błąd - Baza danych może być zablokowana - Sprawdź narzędzia deweloperskie F12 -> Konsola lub spróbuj później.",
|
||||
"Gen_Month": "",
|
||||
"Gen_NetworkMask": "",
|
||||
"Gen_New": "",
|
||||
"Gen_Next": "",
|
||||
@@ -383,6 +386,7 @@
|
||||
"Gen_Update_Value": "Aktualizuj wartość",
|
||||
"Gen_ValidIcon": "<i class=\"fa-solid fa-chevron-right \"></i>",
|
||||
"Gen_Warning": "Ostrzeżenie",
|
||||
"Gen_Week": "",
|
||||
"Gen_Work_In_Progress": "Prace w toku, to dobry moment na przesłanie opinii na https://github.com/netalertx/NetAlertX/issues",
|
||||
"Gen_create_new_device": "Nowe urządzenie",
|
||||
"Gen_create_new_device_info": "Urządzenia są zazwyczaj wykrywane za pomocą <a target=\"_blank\" href=\"https://docs.netalertx.com/PLUGINS\">wtyczek</a>. Jednak w niektórych przypadkach może być konieczne dodanie urządzeń ręcznie. Aby zapoznać się z konkretnymi scenariuszami, sprawdź <a target=\"_blank\" href=\"https://docs.netalertx.com/REMOTE_NETWORKS\">dokumentację dotyczącą sieci zdalnych</a>.",
|
||||
@@ -712,6 +716,14 @@
|
||||
"Systeminfo_Network_Server_Query": "Zapytanie serwerowe:",
|
||||
"Systeminfo_Network_Server_Query_String": "Brak ciągu zapytania",
|
||||
"Systeminfo_Network_Server_Version": "Wersja serwera:",
|
||||
"Systeminfo_Performance": "",
|
||||
"Systeminfo_Performance_CPU": "",
|
||||
"Systeminfo_Performance_Disabled": "",
|
||||
"Systeminfo_Performance_Duration": "",
|
||||
"Systeminfo_Performance_IO_Caption": "",
|
||||
"Systeminfo_Performance_IO_Read": "",
|
||||
"Systeminfo_Performance_IO_Write": "",
|
||||
"Systeminfo_Performance_Memory": "",
|
||||
"Systeminfo_Services": "Usługi",
|
||||
"Systeminfo_Services_Description": "Opis usługi",
|
||||
"Systeminfo_Services_Name": "Nazwa usługi",
|
||||
|
||||
@@ -341,6 +341,7 @@
|
||||
"Gen_Copy": "Executar",
|
||||
"Gen_CopyToClipboard": "",
|
||||
"Gen_DataUpdatedUITakesTime": "OK - Pode levar um tempo para a interface do usuário ser atualizada se uma verificação estiver em execução.",
|
||||
"Gen_Day": "",
|
||||
"Gen_Delete": "Excluir",
|
||||
"Gen_DeleteAll": "Excluir todos",
|
||||
"Gen_Description": "Descrição",
|
||||
@@ -349,9 +350,11 @@
|
||||
"Gen_Filter": "Filtro",
|
||||
"Gen_Flapping": "",
|
||||
"Gen_Generate": "Gerar",
|
||||
"Gen_Hour": "",
|
||||
"Gen_InvalidMac": "",
|
||||
"Gen_Invalid_Value": "",
|
||||
"Gen_LockedDB": "ERRO - O banco de dados pode estar bloqueado - Verifique F12 Ferramentas de desenvolvimento -> Console ou tente mais tarde.",
|
||||
"Gen_Month": "",
|
||||
"Gen_NetworkMask": "",
|
||||
"Gen_New": "",
|
||||
"Gen_Next": "",
|
||||
@@ -383,6 +386,7 @@
|
||||
"Gen_Update_Value": "Atualizar valor",
|
||||
"Gen_ValidIcon": "<i class=\"fa-solid fa-chevron-right \"></i>",
|
||||
"Gen_Warning": "Aviso",
|
||||
"Gen_Week": "",
|
||||
"Gen_Work_In_Progress": "Trabalho em andamento, um bom momento para enviar feedback em https://github.com/netalertx/NetAlertX/issues",
|
||||
"Gen_create_new_device": "Novo dispositivo",
|
||||
"Gen_create_new_device_info": "Os dispositivos são normalmente descobertos usando <a target=\"_blank\" href=\"https://docs.netalertx.com/PLUGINS\">plugins</a>. No entanto, em certos casos, pode ser necessário adicionar dispositivos manualmente. Para explorar cenários específicos, verifique a <a target=\"_blank\" href=\"https://docs.netalertx.com/REMOTE_NETWORKS\">documentação de Redes Remotas</a>.",
|
||||
@@ -712,6 +716,14 @@
|
||||
"Systeminfo_Network_Server_Query": "",
|
||||
"Systeminfo_Network_Server_Query_String": "",
|
||||
"Systeminfo_Network_Server_Version": "",
|
||||
"Systeminfo_Performance": "",
|
||||
"Systeminfo_Performance_CPU": "",
|
||||
"Systeminfo_Performance_Disabled": "",
|
||||
"Systeminfo_Performance_Duration": "",
|
||||
"Systeminfo_Performance_IO_Caption": "",
|
||||
"Systeminfo_Performance_IO_Read": "",
|
||||
"Systeminfo_Performance_IO_Write": "",
|
||||
"Systeminfo_Performance_Memory": "",
|
||||
"Systeminfo_Services": "",
|
||||
"Systeminfo_Services_Description": "",
|
||||
"Systeminfo_Services_Name": "",
|
||||
|
||||
@@ -341,6 +341,7 @@
|
||||
"Gen_Copy": "Executar",
|
||||
"Gen_CopyToClipboard": "Copiar para a área de transferência",
|
||||
"Gen_DataUpdatedUITakesTime": "OK - Pode levar um tempo para a interface do utilizador ser atualizada se uma verificação estiver em execução.",
|
||||
"Gen_Day": "",
|
||||
"Gen_Delete": "Apagar",
|
||||
"Gen_DeleteAll": "Apagar todos",
|
||||
"Gen_Description": "Descrição",
|
||||
@@ -349,9 +350,11 @@
|
||||
"Gen_Filter": "Filtro",
|
||||
"Gen_Flapping": "Flapping",
|
||||
"Gen_Generate": "Gerar",
|
||||
"Gen_Hour": "",
|
||||
"Gen_InvalidMac": "Endereço Mac inválido.",
|
||||
"Gen_Invalid_Value": "Um valor inválido foi inserido",
|
||||
"Gen_LockedDB": "ERRO - A base de dados pode estar bloqueada - Verifique F12 Ferramentas de desenvolvimento -> Console ou tente mais tarde.",
|
||||
"Gen_Month": "",
|
||||
"Gen_NetworkMask": "Máscara de Rede",
|
||||
"Gen_New": "Novo",
|
||||
"Gen_Next": "",
|
||||
@@ -383,6 +386,7 @@
|
||||
"Gen_Update_Value": "Atualizar valor",
|
||||
"Gen_ValidIcon": "<i class=\"fa-solid fa-chevron-right \"></i>",
|
||||
"Gen_Warning": "Aviso",
|
||||
"Gen_Week": "",
|
||||
"Gen_Work_In_Progress": "Trabalho em andamento, um bom momento para enviar feedback em https://github.com/netalertx/NetAlertX/issues",
|
||||
"Gen_create_new_device": "Novo dispositivo",
|
||||
"Gen_create_new_device_info": "Os dispositivos são normalmente descobertos usando <a target=\"_blank\" href=\"https://docs.netalertx.com/PLUGINS\">plugins</a>. No entanto, em certos casos, pode ser necessário adicionar dispositivos manualmente. Para explorar cenários específicos, verifique a <a target=\"_blank\" href=\"https://docs.netalertx.com/REMOTE_NETWORKS\">documentação de Redes Remotas</a>.",
|
||||
@@ -712,6 +716,14 @@
|
||||
"Systeminfo_Network_Server_Query": "Consulta de servidor:",
|
||||
"Systeminfo_Network_Server_Query_String": "Nenhuma string de consulta",
|
||||
"Systeminfo_Network_Server_Version": "Versão do Servidor:",
|
||||
"Systeminfo_Performance": "",
|
||||
"Systeminfo_Performance_CPU": "",
|
||||
"Systeminfo_Performance_Disabled": "",
|
||||
"Systeminfo_Performance_Duration": "",
|
||||
"Systeminfo_Performance_IO_Caption": "",
|
||||
"Systeminfo_Performance_IO_Read": "",
|
||||
"Systeminfo_Performance_IO_Write": "",
|
||||
"Systeminfo_Performance_Memory": "",
|
||||
"Systeminfo_Services": "Serviços",
|
||||
"Systeminfo_Services_Description": "Descrição do Serviço",
|
||||
"Systeminfo_Services_Name": "Nome do Serviço",
|
||||
|
||||
@@ -341,6 +341,7 @@
|
||||
"Gen_Copy": "Запустить",
|
||||
"Gen_CopyToClipboard": "Копировать в буфер обмена",
|
||||
"Gen_DataUpdatedUITakesTime": "ОК - Обновление UI может занять некоторое время, если сканирование выполняется.",
|
||||
"Gen_Day": "",
|
||||
"Gen_Delete": "Удалить",
|
||||
"Gen_DeleteAll": "Удалить все",
|
||||
"Gen_Description": "Описание",
|
||||
@@ -349,9 +350,11 @@
|
||||
"Gen_Filter": "Фильтр",
|
||||
"Gen_Flapping": "Нестабильный",
|
||||
"Gen_Generate": "Генерировать",
|
||||
"Gen_Hour": "",
|
||||
"Gen_InvalidMac": "Неверный Mac-адрес.",
|
||||
"Gen_Invalid_Value": "Введено некорректное значение",
|
||||
"Gen_LockedDB": "ОШИБКА - Возможно, база данных заблокирована. Проверьте инструменты разработчика F12 -> Консоль или повторите попытку позже.",
|
||||
"Gen_Month": "",
|
||||
"Gen_NetworkMask": "Маска сети",
|
||||
"Gen_New": "Новый",
|
||||
"Gen_Next": "Следующий",
|
||||
@@ -383,6 +386,7 @@
|
||||
"Gen_Update_Value": "Обновить значение",
|
||||
"Gen_ValidIcon": "<i class=\"fa-solid fa-chevron-right \"></i>",
|
||||
"Gen_Warning": "Предупреждение",
|
||||
"Gen_Week": "",
|
||||
"Gen_Work_In_Progress": "Работа продолжается, самое время оставить отзыв на https://github.com/netalertx/NetAlertX/issues",
|
||||
"Gen_create_new_device": "Новое устройство",
|
||||
"Gen_create_new_device_info": "Устройства обычно обнаруживаются с помощью <a target=\"_blank\" href=\"https://docs.netalertx.com/PLUGINS\">плагинов</a>. Однако в некоторых случаях вам может потребоваться добавить устройства вручную. Для изучения конкретных сценариев ознакомьтесь с документацией <a target=\"_blank\" href=\"https://docs.netalertx.com/REMOTE_NETWORKS\">Remote Networks</a>.",
|
||||
@@ -712,6 +716,14 @@
|
||||
"Systeminfo_Network_Server_Query": "Запрос сервера:",
|
||||
"Systeminfo_Network_Server_Query_String": "Нет строки запроса",
|
||||
"Systeminfo_Network_Server_Version": "Версия сервера:",
|
||||
"Systeminfo_Performance": "",
|
||||
"Systeminfo_Performance_CPU": "",
|
||||
"Systeminfo_Performance_Disabled": "",
|
||||
"Systeminfo_Performance_Duration": "",
|
||||
"Systeminfo_Performance_IO_Caption": "",
|
||||
"Systeminfo_Performance_IO_Read": "",
|
||||
"Systeminfo_Performance_IO_Write": "",
|
||||
"Systeminfo_Performance_Memory": "",
|
||||
"Systeminfo_Services": "Службы",
|
||||
"Systeminfo_Services_Description": "Описание службы",
|
||||
"Systeminfo_Services_Name": "Имя службы",
|
||||
|
||||
@@ -341,6 +341,7 @@
|
||||
"Gen_Copy": "",
|
||||
"Gen_CopyToClipboard": "",
|
||||
"Gen_DataUpdatedUITakesTime": "",
|
||||
"Gen_Day": "",
|
||||
"Gen_Delete": "",
|
||||
"Gen_DeleteAll": "",
|
||||
"Gen_Description": "",
|
||||
@@ -349,9 +350,11 @@
|
||||
"Gen_Filter": "",
|
||||
"Gen_Flapping": "",
|
||||
"Gen_Generate": "",
|
||||
"Gen_Hour": "",
|
||||
"Gen_InvalidMac": "",
|
||||
"Gen_Invalid_Value": "",
|
||||
"Gen_LockedDB": "",
|
||||
"Gen_Month": "",
|
||||
"Gen_NetworkMask": "",
|
||||
"Gen_New": "",
|
||||
"Gen_Next": "",
|
||||
@@ -383,6 +386,7 @@
|
||||
"Gen_Update_Value": "",
|
||||
"Gen_ValidIcon": "",
|
||||
"Gen_Warning": "",
|
||||
"Gen_Week": "",
|
||||
"Gen_Work_In_Progress": "",
|
||||
"Gen_create_new_device": "",
|
||||
"Gen_create_new_device_info": "",
|
||||
@@ -712,6 +716,14 @@
|
||||
"Systeminfo_Network_Server_Query": "",
|
||||
"Systeminfo_Network_Server_Query_String": "",
|
||||
"Systeminfo_Network_Server_Version": "",
|
||||
"Systeminfo_Performance": "",
|
||||
"Systeminfo_Performance_CPU": "",
|
||||
"Systeminfo_Performance_Disabled": "",
|
||||
"Systeminfo_Performance_Duration": "",
|
||||
"Systeminfo_Performance_IO_Caption": "",
|
||||
"Systeminfo_Performance_IO_Read": "",
|
||||
"Systeminfo_Performance_IO_Write": "",
|
||||
"Systeminfo_Performance_Memory": "",
|
||||
"Systeminfo_Services": "",
|
||||
"Systeminfo_Services_Description": "",
|
||||
"Systeminfo_Services_Name": "",
|
||||
|
||||
@@ -341,6 +341,7 @@
|
||||
"Gen_Copy": "Çalıştır",
|
||||
"Gen_CopyToClipboard": "",
|
||||
"Gen_DataUpdatedUITakesTime": "TAMAM - Eğer bir tarama çalışıyorsa arayüzün güncellenmesi biraz zaman alabilir.",
|
||||
"Gen_Day": "",
|
||||
"Gen_Delete": "Sil",
|
||||
"Gen_DeleteAll": "Tümünü sil",
|
||||
"Gen_Description": "Açıklama",
|
||||
@@ -349,9 +350,11 @@
|
||||
"Gen_Filter": "Filtre",
|
||||
"Gen_Flapping": "",
|
||||
"Gen_Generate": "Oluştur",
|
||||
"Gen_Hour": "",
|
||||
"Gen_InvalidMac": "",
|
||||
"Gen_Invalid_Value": "",
|
||||
"Gen_LockedDB": "HATA - Veritabanı kilitlenmiş olabilir - F12 Geliştirici araçlarını -> Konsol kısmını kontrol edin veya daha sonra tekrar deneyin.",
|
||||
"Gen_Month": "",
|
||||
"Gen_NetworkMask": "",
|
||||
"Gen_New": "",
|
||||
"Gen_Next": "",
|
||||
@@ -383,6 +386,7 @@
|
||||
"Gen_Update_Value": "Değeri Güncelle",
|
||||
"Gen_ValidIcon": "<i class=\"fa-solid fa-chevron-right \"></i>",
|
||||
"Gen_Warning": "Uyarı",
|
||||
"Gen_Week": "",
|
||||
"Gen_Work_In_Progress": "Çalışma devam ediyor, geri bildirimde bulunmak için iyi bir zaman: https://github.com/netalertx/NetAlertX/issues",
|
||||
"Gen_create_new_device": "Yeni cihaz",
|
||||
"Gen_create_new_device_info": "Cihazlar genellikle <a target=\"_blank\" href=\"https://docs.netalertx.com/PLUGINS\">eklentiler</a> kullanılarak keşfedilir. Ancak, bazı durumlarda cihazları manuel olarak eklemeniz gerekebilir. Belirli senaryoları incelemek için <a target=\"_blank\" href=\"https://docs.netalertx.com/REMOTE_NETWORKS\">Uzak Ağlar belgelerini</a> kontrol edin.",
|
||||
@@ -712,6 +716,14 @@
|
||||
"Systeminfo_Network_Server_Query": "",
|
||||
"Systeminfo_Network_Server_Query_String": "",
|
||||
"Systeminfo_Network_Server_Version": "",
|
||||
"Systeminfo_Performance": "",
|
||||
"Systeminfo_Performance_CPU": "",
|
||||
"Systeminfo_Performance_Disabled": "",
|
||||
"Systeminfo_Performance_Duration": "",
|
||||
"Systeminfo_Performance_IO_Caption": "",
|
||||
"Systeminfo_Performance_IO_Read": "",
|
||||
"Systeminfo_Performance_IO_Write": "",
|
||||
"Systeminfo_Performance_Memory": "",
|
||||
"Systeminfo_Services": "",
|
||||
"Systeminfo_Services_Description": "",
|
||||
"Systeminfo_Services_Name": "",
|
||||
|
||||
@@ -341,6 +341,7 @@
|
||||
"Gen_Copy": "Запустити",
|
||||
"Gen_CopyToClipboard": "Копіювати в буфер обміну",
|
||||
"Gen_DataUpdatedUITakesTime": "Добре. Оновлення інтерфейсу може зайняти деякий час, якщо сканування виконується.",
|
||||
"Gen_Day": "",
|
||||
"Gen_Delete": "Видалити",
|
||||
"Gen_DeleteAll": "Видалити все",
|
||||
"Gen_Description": "Опис",
|
||||
@@ -349,9 +350,11 @@
|
||||
"Gen_Filter": "Фільтр",
|
||||
"Gen_Flapping": "",
|
||||
"Gen_Generate": "Генерувати",
|
||||
"Gen_Hour": "",
|
||||
"Gen_InvalidMac": "Недійсна Mac-адреса.",
|
||||
"Gen_Invalid_Value": "Введено недійсне значення",
|
||||
"Gen_LockedDB": "ПОМИЛКА – БД може бути заблоковано – перевірте F12 Інструменти розробника -> Консоль або спробуйте пізніше.",
|
||||
"Gen_Month": "",
|
||||
"Gen_NetworkMask": "Маска мережі",
|
||||
"Gen_New": "",
|
||||
"Gen_Next": "",
|
||||
@@ -383,6 +386,7 @@
|
||||
"Gen_Update_Value": "Оновити значення",
|
||||
"Gen_ValidIcon": "<i class=\"fa-solid fa-chevron-right \"></i>",
|
||||
"Gen_Warning": "Попередження",
|
||||
"Gen_Week": "",
|
||||
"Gen_Work_In_Progress": "Робота триває, час залишити відгук на https://github.com/netalertx/NetAlertX/issues",
|
||||
"Gen_create_new_device": "Новий пристрій",
|
||||
"Gen_create_new_device_info": "Пристрої зазвичай виявляють за допомогою <a target=\"_blank\" href=\"https://docs.netalertx.com/PLUGINS\">плагінів</a>. Однак у деяких випадках може знадобитися додати пристрої вручну. Щоб дослідити конкретні сценарії, перегляньте <a target=\"_blank\" href=\"https://docs.netalertx.com/REMOTE_NETWORKS\">документацію щодо віддалених мереж</a>.",
|
||||
@@ -712,6 +716,14 @@
|
||||
"Systeminfo_Network_Server_Query": "Запит до сервера:",
|
||||
"Systeminfo_Network_Server_Query_String": "Немає рядка запиту",
|
||||
"Systeminfo_Network_Server_Version": "Версія сервера:",
|
||||
"Systeminfo_Performance": "",
|
||||
"Systeminfo_Performance_CPU": "",
|
||||
"Systeminfo_Performance_Disabled": "",
|
||||
"Systeminfo_Performance_Duration": "",
|
||||
"Systeminfo_Performance_IO_Caption": "",
|
||||
"Systeminfo_Performance_IO_Read": "",
|
||||
"Systeminfo_Performance_IO_Write": "",
|
||||
"Systeminfo_Performance_Memory": "",
|
||||
"Systeminfo_Services": "Послуги",
|
||||
"Systeminfo_Services_Description": "Опис послуги",
|
||||
"Systeminfo_Services_Name": "Назва служби",
|
||||
|
||||
@@ -341,6 +341,7 @@
|
||||
"Gen_Copy": "",
|
||||
"Gen_CopyToClipboard": "",
|
||||
"Gen_DataUpdatedUITakesTime": "",
|
||||
"Gen_Day": "",
|
||||
"Gen_Delete": "",
|
||||
"Gen_DeleteAll": "",
|
||||
"Gen_Description": "",
|
||||
@@ -349,9 +350,11 @@
|
||||
"Gen_Filter": "",
|
||||
"Gen_Flapping": "",
|
||||
"Gen_Generate": "",
|
||||
"Gen_Hour": "",
|
||||
"Gen_InvalidMac": "",
|
||||
"Gen_Invalid_Value": "",
|
||||
"Gen_LockedDB": "",
|
||||
"Gen_Month": "",
|
||||
"Gen_NetworkMask": "",
|
||||
"Gen_New": "",
|
||||
"Gen_Next": "",
|
||||
@@ -383,6 +386,7 @@
|
||||
"Gen_Update_Value": "",
|
||||
"Gen_ValidIcon": "",
|
||||
"Gen_Warning": "",
|
||||
"Gen_Week": "",
|
||||
"Gen_Work_In_Progress": "",
|
||||
"Gen_create_new_device": "",
|
||||
"Gen_create_new_device_info": "",
|
||||
@@ -712,6 +716,14 @@
|
||||
"Systeminfo_Network_Server_Query": "",
|
||||
"Systeminfo_Network_Server_Query_String": "",
|
||||
"Systeminfo_Network_Server_Version": "",
|
||||
"Systeminfo_Performance": "",
|
||||
"Systeminfo_Performance_CPU": "",
|
||||
"Systeminfo_Performance_Disabled": "",
|
||||
"Systeminfo_Performance_Duration": "",
|
||||
"Systeminfo_Performance_IO_Caption": "",
|
||||
"Systeminfo_Performance_IO_Read": "",
|
||||
"Systeminfo_Performance_IO_Write": "",
|
||||
"Systeminfo_Performance_Memory": "",
|
||||
"Systeminfo_Services": "",
|
||||
"Systeminfo_Services_Description": "",
|
||||
"Systeminfo_Services_Name": "",
|
||||
|
||||
@@ -341,6 +341,7 @@
|
||||
"Gen_Copy": "运行",
|
||||
"Gen_CopyToClipboard": "复制到剪贴板",
|
||||
"Gen_DataUpdatedUITakesTime": "好的 - 如果扫描正在运行,UI 可能需要一段时间才能更新。",
|
||||
"Gen_Day": "",
|
||||
"Gen_Delete": "删除",
|
||||
"Gen_DeleteAll": "全部删除",
|
||||
"Gen_Description": "描述",
|
||||
@@ -349,9 +350,11 @@
|
||||
"Gen_Filter": "筛选",
|
||||
"Gen_Flapping": "跳动",
|
||||
"Gen_Generate": "生成",
|
||||
"Gen_Hour": "",
|
||||
"Gen_InvalidMac": "无效的 Mac 地址。",
|
||||
"Gen_Invalid_Value": "输入了无效的值",
|
||||
"Gen_LockedDB": "错误 - DB 可能被锁定 - 检查 F12 开发工具 -> 控制台或稍后重试。",
|
||||
"Gen_Month": "",
|
||||
"Gen_NetworkMask": "网络掩码",
|
||||
"Gen_New": "新的",
|
||||
"Gen_Next": "",
|
||||
@@ -383,6 +386,7 @@
|
||||
"Gen_Update_Value": "更新值",
|
||||
"Gen_ValidIcon": "<i class=\"fa-solid fa-chevron-right \"></i>",
|
||||
"Gen_Warning": "警告",
|
||||
"Gen_Week": "",
|
||||
"Gen_Work_In_Progress": "工作正在进行中,欢迎在 https://github.com/netalertx/NetAlertX/issues 上反馈",
|
||||
"Gen_create_new_device": "新设备",
|
||||
"Gen_create_new_device_info": "通常使用<a target=\"_blank\" href=\"https://docs.netalertx.com/PLUGINS\">plugins</a>来发现设备。但是,在某些情况下,您可能需要手动添加设备。要探索特定场景,请查看<a target=\"_blank\" href=\"https://docs.netalertx.com/REMOTE_NETWORKS\">远程网络文档</a>。",
|
||||
@@ -712,6 +716,14 @@
|
||||
"Systeminfo_Network_Server_Query": "服务器查询:",
|
||||
"Systeminfo_Network_Server_Query_String": "没有查询到字符串",
|
||||
"Systeminfo_Network_Server_Version": "服务器版本:",
|
||||
"Systeminfo_Performance": "",
|
||||
"Systeminfo_Performance_CPU": "",
|
||||
"Systeminfo_Performance_Disabled": "",
|
||||
"Systeminfo_Performance_Duration": "",
|
||||
"Systeminfo_Performance_IO_Caption": "",
|
||||
"Systeminfo_Performance_IO_Read": "",
|
||||
"Systeminfo_Performance_IO_Write": "",
|
||||
"Systeminfo_Performance_Memory": "",
|
||||
"Systeminfo_Services": "服务",
|
||||
"Systeminfo_Services_Description": "服务说明",
|
||||
"Systeminfo_Services_Name": "服务名称",
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
<div class="skel-info-tab spinnerTarget">
|
||||
<div class="skel-table-header-row">
|
||||
<?php for ($i = 0; $i < 4; $i++): ?>
|
||||
<span class="skel-th skel-shimmer"></span>
|
||||
<?php endfor; ?>
|
||||
</div>
|
||||
<?php for ($i = 0; $i < 4; $i++): ?>
|
||||
<span class="skel-shimmer" style="height:150px; width:100%; display:block; margin-bottom:15px; border-radius:4px;"></span>
|
||||
<?php endfor; ?>
|
||||
</div>
|
||||
@@ -15,6 +15,10 @@
|
||||
?>
|
||||
<!-- ----------------------------------------------------------------------- -->
|
||||
|
||||
<!-- Loaded here (not in systeminfoPerformance.php itself) so it's available
|
||||
before that tab's AJAX-fetched content runs - matching presence.php/
|
||||
devices.php's own graph component pattern. -->
|
||||
<script src="lib/chart.js/Chart.js"></script>
|
||||
|
||||
<script>
|
||||
|
||||
@@ -65,6 +69,14 @@
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a id="tabPerformance" href="#panPerformance" data-toggle="tab">
|
||||
<i class="fa fa-chart-line"></i>
|
||||
<span class="dev-detail-tab-name">
|
||||
<?= lang('Systeminfo_Performance');?>
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="tab-content spinnerTarget">
|
||||
@@ -80,6 +92,9 @@
|
||||
<div class="tab-pane fade" data-php-file="systeminfoInitCheck.php" id="panInitCheck">
|
||||
<?php require 'php/templates/skel_tab_sysinfo_initcheck.php'; ?>
|
||||
</div>
|
||||
<div class="tab-pane fade" data-php-file="systeminfoPerformance.php" id="panPerformance">
|
||||
<?php require 'php/templates/skel_tab_sysinfo_performance.php'; ?>
|
||||
</div>
|
||||
</div>
|
||||
<!-- /.tab-content -->
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
//------------------------------------------------------------------------------
|
||||
// check if authenticated
|
||||
require_once $_SERVER['DOCUMENT_ROOT'] . '/php/templates/security.php';
|
||||
require_once $_SERVER['DOCUMENT_ROOT'] . '/php/server/db.php';
|
||||
require_once $_SERVER['DOCUMENT_ROOT'] . '/php/templates/language/lang.php';
|
||||
?>
|
||||
|
||||
<script src="js/graph_resource_history.js"></script>
|
||||
|
||||
<div class="box box-solid">
|
||||
<div class="box-header">
|
||||
<h3 class="box-title sysinfo_headline"><i class="fa fa-chart-line"></i> <?= lang('Systeminfo_Performance');?>
|
||||
<a href="https://docs.netalertx.com/PERFORMANCE" target="_blank"><i class="fa fa-circle-question"></i></a>
|
||||
</h3>
|
||||
</div>
|
||||
<div class="box-body">
|
||||
|
||||
<div class="btn-group" role="group" id="resourceHistoryRangeGroup" style="margin-bottom: 15px;">
|
||||
<button type="button" class="btn btn-default resource-history-range-btn" data-range="hour"><?= lang('Gen_Hour');?></button>
|
||||
<button type="button" class="btn btn-default resource-history-range-btn active" data-range="day"><?= lang('Gen_Day');?></button>
|
||||
<button type="button" class="btn btn-default resource-history-range-btn" data-range="week"><?= lang('Gen_Week');?></button>
|
||||
<button type="button" class="btn btn-default resource-history-range-btn" data-range="month"><?= lang('Gen_Month');?></button>
|
||||
</div>
|
||||
|
||||
<div id="resourceHistoryDisabledMessage" class="myhidden" style="padding: 15px;">
|
||||
<?= lang('Systeminfo_Performance_Disabled');?>
|
||||
</div>
|
||||
|
||||
<div id="resourceHistoryCharts">
|
||||
<div style="position: relative; height: 150px; width: 100%; margin-bottom: 25px;">
|
||||
<canvas id="ResourceCpuChart"></canvas>
|
||||
</div>
|
||||
<div style="position: relative; height: 150px; width: 100%; margin-bottom: 25px;">
|
||||
<canvas id="ResourceRssChart"></canvas>
|
||||
</div>
|
||||
<div style="position: relative; height: 150px; width: 100%; margin-bottom: 5px;">
|
||||
<canvas id="ResourceIoChart"></canvas>
|
||||
</div>
|
||||
<p class="text-muted" style="font-size: 12px; margin-bottom: 25px;"><?= lang('Systeminfo_Performance_IO_Caption');?></p>
|
||||
<div style="position: relative; height: 150px; width: 100%; margin-bottom: 15px;">
|
||||
<canvas id="ResourceDurationChart"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
|
||||
if (isAppInitialized()) {
|
||||
initResourceHistoryGraphs('day');
|
||||
} else {
|
||||
callAfterAppInitialized(() => initResourceHistoryGraphs('day'));
|
||||
}
|
||||
|
||||
$('#resourceHistoryRangeGroup .resource-history-range-btn').on('click', function () {
|
||||
$('#resourceHistoryRangeGroup .resource-history-range-btn').removeClass('active');
|
||||
$(this).addClass('active');
|
||||
initResourceHistoryGraphs($(this).data('range'));
|
||||
});
|
||||
|
||||
hideSpinner();
|
||||
|
||||
</script>
|
||||
@@ -10,6 +10,54 @@
|
||||
|
||||
<?php
|
||||
|
||||
// ----------------------------------------------------------
|
||||
// Database (moved here from maintenance.php - both are storage-facts)
|
||||
// ----------------------------------------------------------
|
||||
|
||||
$dbBasePath = rtrim(getenv('NETALERTX_DB') ?: '/data/db', '/');
|
||||
$nax_db = $dbBasePath . '/app.db';
|
||||
$nax_wal = $dbBasePath . '/app.db-wal';
|
||||
$nax_db_size = file_exists($nax_db) ? number_format((filesize($nax_db) / 1000000), 2, ",", ".") . ' MB' : '0 MB';
|
||||
$nax_wal_size = file_exists($nax_wal) ? number_format((filesize($nax_wal) / 1000000), 2, ",", ".") . ' MB' : '0 MB';
|
||||
$nax_db_mod = file_exists($nax_db) ? date("F d Y H:i:s", filemtime($nax_db)) : 'N/A';
|
||||
|
||||
// Table row counts
|
||||
$tableSizesHTML = "";
|
||||
$db_info_conn = new SQLite3($nax_db);
|
||||
$table_names_result = $db_info_conn->query("SELECT name FROM sqlite_master WHERE type='table'");
|
||||
while ($row = $table_names_result->fetchArray(SQLITE3_ASSOC)) {
|
||||
$tableName = $row['name'];
|
||||
$countResult = $db_info_conn->querySingle("SELECT COUNT(*) FROM $tableName");
|
||||
$tableSizesHTML = $tableSizesHTML . "$tableName (<b>$countResult</b>), ";
|
||||
}
|
||||
$db_info_conn->close();
|
||||
|
||||
echo '<div class="box box-solid">
|
||||
<div class="box-header">
|
||||
<h3 class="box-title sysinfo_headline"><i class="fa fa-database"></i> ' . lang('Maintenance_Status') . '</h3>
|
||||
</div>
|
||||
<div class="box-body">
|
||||
<div class="db_info_table">
|
||||
<div class="db_info_table_row">
|
||||
<div class="db_info_table_cell" style="min-width: 140px">' . lang('Maintenance_database_path') . '</div>
|
||||
<div class="db_info_table_cell">' . $nax_db . '</div>
|
||||
</div>
|
||||
<div class="db_info_table_row">
|
||||
<div class="db_info_table_cell">' . lang('Maintenance_database_size') . '</div>
|
||||
<div class="db_info_table_cell">' . $nax_db_size . ' (wal: ' . $nax_wal_size . ')</div>
|
||||
</div>
|
||||
<div class="db_info_table_row">
|
||||
<div class="db_info_table_cell">' . lang('Maintenance_database_rows') . '</div>
|
||||
<div class="db_info_table_cell">' . $tableSizesHTML . '</div>
|
||||
</div>
|
||||
<div class="db_info_table_row">
|
||||
<div class="db_info_table_cell">' . lang('Maintenance_database_lastmod') . '</div>
|
||||
<div class="db_info_table_cell">' . $nax_db_mod . '</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>';
|
||||
|
||||
// ----------------------------------------------------------
|
||||
// Storage
|
||||
// ----------------------------------------------------------
|
||||
|
||||
+51
-30
@@ -25,11 +25,12 @@ from pathlib import Path
|
||||
import conf
|
||||
from const import fullConfPath, sql_new_devices
|
||||
from logger import mylog
|
||||
from helper import filePermissions
|
||||
from helper import filePermissions, get_setting_value
|
||||
from utils.datetime_utils import timeNowUTC, is_datetime_future, normalizeTimeStamp
|
||||
from app_state import updateState
|
||||
from api import update_api, check_activity, update_GUI_port
|
||||
from scan.session_events import process_scan
|
||||
from scan.resource_history import get_process_cpu_times_and_io, insert_resource_history
|
||||
from initialise import importConfigs, renameSettings
|
||||
from database import DB
|
||||
from messaging.reporting import get_notifications
|
||||
@@ -149,43 +150,63 @@ def main():
|
||||
# last time any scan or maintenance/upkeep was run
|
||||
conf.last_scan_run = loop_start_time
|
||||
|
||||
# Header (also broadcasts last_scan_run to frontend via SSE / app_state.json)
|
||||
updateState("Process: Start",
|
||||
last_scan_run=loop_start_time.replace(microsecond=0).isoformat(),
|
||||
next_scan_time="")
|
||||
# Resource-usage sampling: brackets the whole schedule-tick block
|
||||
# below (run_plugin_scripts("schedule") through process_scan()),
|
||||
# not just the scan pipeline itself - plugin subprocess execution
|
||||
# happens in this window and is the class of cost this history
|
||||
# exists to catch. Gated on MAINT_PERF_DAYS so a disabled install
|
||||
# pays no sampling cost at all (see scan/resource_history.py).
|
||||
resource_history_enabled = int(get_setting_value("MAINT_PERF_DAYS", 30)) != 0
|
||||
resource_pre = get_process_cpu_times_and_io() if resource_history_enabled else None
|
||||
tick_start_monotonic = time.monotonic()
|
||||
tick_failed = False
|
||||
|
||||
# Timestamp
|
||||
startTime = loop_start_time
|
||||
startTime = startTime.replace(microsecond=0)
|
||||
try:
|
||||
# Header (also broadcasts last_scan_run to frontend via SSE / app_state.json)
|
||||
updateState("Process: Start",
|
||||
last_scan_run=loop_start_time.replace(microsecond=0).isoformat(),
|
||||
next_scan_time="")
|
||||
|
||||
# Check if any plugins need to run on schedule
|
||||
pm.run_plugin_scripts("schedule")
|
||||
# Timestamp
|
||||
startTime = loop_start_time
|
||||
startTime = startTime.replace(microsecond=0)
|
||||
|
||||
# Compute the next scheduled run time AFTER schedule check (which updates last_next_schedule)
|
||||
# Only device_scanner plugins have meaningful next_scan times for user display
|
||||
scanner_prefixes = {p["unique_prefix"] for p in all_plugins if p.get("plugin_type") == "device_scanner"}
|
||||
scanner_next = [s.last_next_schedule for s in conf.mySchedules if s.service in scanner_prefixes]
|
||||
# Check if any plugins need to run on schedule
|
||||
pm.run_plugin_scripts("schedule")
|
||||
|
||||
# Get the earliest next scan time across all device scanners and broadcast.
|
||||
# updateState validates the value is in the future before storing/broadcasting.
|
||||
if scanner_next:
|
||||
next_scan_dt = min(scanner_next)
|
||||
updateState(next_scan_time=next_scan_dt.replace(microsecond=0).isoformat())
|
||||
# Compute the next scheduled run time AFTER schedule check (which updates last_next_schedule)
|
||||
# Only device_scanner plugins have meaningful next_scan times for user display
|
||||
scanner_prefixes = {p["unique_prefix"] for p in all_plugins if p.get("plugin_type") == "device_scanner"}
|
||||
scanner_next = [s.last_next_schedule for s in conf.mySchedules if s.service in scanner_prefixes]
|
||||
|
||||
# determine run/scan type based on passed time
|
||||
# --------------------------------------------
|
||||
# Get the earliest next scan time across all device scanners and broadcast.
|
||||
# updateState validates the value is in the future before storing/broadcasting.
|
||||
if scanner_next:
|
||||
next_scan_dt = min(scanner_next)
|
||||
updateState(next_scan_time=next_scan_dt.replace(microsecond=0).isoformat())
|
||||
|
||||
# Runs plugin scripts which are set to run every time after a scans finished
|
||||
pm.run_plugin_scripts("always_after_scan")
|
||||
# determine run/scan type based on passed time
|
||||
# --------------------------------------------
|
||||
|
||||
# process all the scanned data into new devices
|
||||
processScan = updateState("Check scan").processScan
|
||||
mylog("debug", [f"[MAIN] processScan: {processScan}"])
|
||||
# Runs plugin scripts which are set to run every time after a scans finished
|
||||
pm.run_plugin_scripts("always_after_scan")
|
||||
|
||||
if processScan is True:
|
||||
mylog("debug", "[MAIN] start processing scan results")
|
||||
process_scan(db)
|
||||
updateState("Scan processed", None, None, None, None, False)
|
||||
# process all the scanned data into new devices
|
||||
processScan = updateState("Check scan").processScan
|
||||
mylog("debug", [f"[MAIN] processScan: {processScan}"])
|
||||
|
||||
if processScan is True:
|
||||
mylog("debug", "[MAIN] start processing scan results")
|
||||
process_scan(db)
|
||||
updateState("Scan processed", None, None, None, None, False)
|
||||
except Exception:
|
||||
tick_failed = True
|
||||
raise
|
||||
finally:
|
||||
if resource_history_enabled:
|
||||
duration_ms = int((time.monotonic() - tick_start_monotonic) * 1000)
|
||||
resource_post = get_process_cpu_times_and_io()
|
||||
insert_resource_history(db, resource_pre, resource_post, duration_ms, tick_failed)
|
||||
|
||||
# Name resolution
|
||||
# --------------------------------------------
|
||||
|
||||
@@ -21,6 +21,10 @@ from const import (
|
||||
sql_language_strings,
|
||||
sql_notifications_all,
|
||||
sql_online_history,
|
||||
sql_resource_history_hour,
|
||||
sql_resource_history_day,
|
||||
sql_resource_history_week,
|
||||
sql_resource_history_month,
|
||||
sql_devices_filters,
|
||||
defaultWebPort,
|
||||
)
|
||||
@@ -75,6 +79,10 @@ def update_api(
|
||||
["plugins_language_strings", sql_language_strings],
|
||||
["notifications", sql_notifications_all],
|
||||
["online_history", sql_online_history],
|
||||
["resource_history_hour", sql_resource_history_hour],
|
||||
["resource_history_day", sql_resource_history_day],
|
||||
["resource_history_week", sql_resource_history_week],
|
||||
["resource_history_month", sql_resource_history_month],
|
||||
["devices_tiles", get_sql_devices_tiles()],
|
||||
["devices_filters", sql_devices_filters],
|
||||
["custom_endpoint", conf.API_CUSTOM_SQL],
|
||||
|
||||
@@ -2058,7 +2058,7 @@ def check_auth(payload=None):
|
||||
@validate_request(
|
||||
operation_id="check_health",
|
||||
summary="System Health Check",
|
||||
description="Retrieve system vitality metrics including database size, memory pressure, system load, disk usage, and CPU temperature.",
|
||||
description="Retrieve system vitality metrics including database size, memory pressure, system load, disk usage, CPU temperature, and the NetAlertX process's own CPU%/RSS.",
|
||||
response_model=HealthCheckResponse,
|
||||
tags=["system", "health"],
|
||||
auth_callable=is_authorized
|
||||
|
||||
@@ -155,6 +155,48 @@ def get_storage_gb():
|
||||
return None
|
||||
|
||||
|
||||
# ===============================================================================
|
||||
# Process Vitality (NetAlertX's own process, not the host)
|
||||
# ===============================================================================
|
||||
|
||||
# Non-blocking cpu_percent() needs one persistent Process object to mean anything
|
||||
# ("since the last time this was called") - this object is dedicated to /health only,
|
||||
# never shared with the tick-scoped sampler in scan/resource_history.py, so the two
|
||||
# measurement paths can't interfere with each other.
|
||||
_health_process = psutil.Process()
|
||||
|
||||
|
||||
def get_process_cpu_percent():
|
||||
"""
|
||||
CPU% for the NetAlertX process since the last time this was called - a live
|
||||
gauge for /health, not a tick-scoped measurement (see scan/resource_history.py's
|
||||
tick-scoped sampler for that).
|
||||
|
||||
Returns:
|
||||
float: CPU utilization percentage, or 0.0 on error.
|
||||
"""
|
||||
try:
|
||||
return _health_process.cpu_percent(interval=None)
|
||||
except (psutil.Error, AttributeError) as e:
|
||||
mylog("verbose", [f"[health] Error reading process CPU%: {e}"])
|
||||
return 0.0
|
||||
|
||||
|
||||
def get_process_rss_mb():
|
||||
"""
|
||||
Resident memory (RSS) for the current process, in MB - a point-in-time
|
||||
snapshot, not a delta (RSS doesn't accumulate the way CPU-time/IO do).
|
||||
|
||||
Returns:
|
||||
float: RSS in MB, or 0.0 on error.
|
||||
"""
|
||||
try:
|
||||
return round(psutil.Process().memory_info().rss / (1024 * 1024), 2)
|
||||
except (psutil.Error, AttributeError) as e:
|
||||
mylog("verbose", [f"[health] Error reading process RSS: {e}"])
|
||||
return 0.0
|
||||
|
||||
|
||||
# ===============================================================================
|
||||
# Aggregator
|
||||
# ===============================================================================
|
||||
@@ -174,4 +216,6 @@ def get_health_status():
|
||||
"cpu_temp": get_cpu_temp(),
|
||||
"storage_gb": get_storage_gb(),
|
||||
"mem_mb": get_mem_mb(),
|
||||
"process_cpu_pct": get_process_cpu_percent(),
|
||||
"process_rss_mb": get_process_rss_mb(),
|
||||
}
|
||||
@@ -718,7 +718,9 @@ class HealthCheckResponse(BaseResponse):
|
||||
"storage_pct": 42,
|
||||
"cpu_temp": 58,
|
||||
"storage_gb": 8,
|
||||
"mem_mb" : 8192
|
||||
"mem_mb" : 8192,
|
||||
"process_cpu_pct": 3.2,
|
||||
"process_rss_mb": 142.75
|
||||
}]
|
||||
}
|
||||
)
|
||||
@@ -730,6 +732,8 @@ class HealthCheckResponse(BaseResponse):
|
||||
cpu_temp: Optional[int] = Field(None, description="CPU temperature in Celsius (nullable if unavailable)")
|
||||
storage_gb: Optional[int] = Field(..., description="Storage size in GB")
|
||||
mem_mb: Optional[int] = Field(..., description="Installed memory size in MB")
|
||||
process_cpu_pct: float = Field(..., description="NetAlertX process CPU usage percentage since the last /health call (live gauge, not a tick average)")
|
||||
process_rss_mb: float = Field(..., description="NetAlertX process resident memory (RSS) in MB")
|
||||
|
||||
|
||||
# =============================================================================
|
||||
|
||||
@@ -136,6 +136,45 @@ sql_plugins_stats = """SELECT 'objects' AS tableName, plugin, COUNT(*) AS cnt FR
|
||||
sql_language_strings = "SELECT * FROM Plugins_Language_Strings"
|
||||
sql_notifications_all = "SELECT * FROM Notifications"
|
||||
sql_online_history = "SELECT * FROM Online_History"
|
||||
|
||||
# Resource_History read-side views (System Info -> Performance tab).
|
||||
# hour/day return raw per-tick rows (cheap indexed range scan); week/month
|
||||
# roll up at query time into hourly buckets (SUM for additive IO bytes,
|
||||
# AVG for CPU%/RSS/duration) - no pre-aggregated rollup table, see
|
||||
# resource-usage-history PRD Design §4. Deltas are already stored per-row
|
||||
# by insert_resource_history(), so no LAG()/window function is needed here.
|
||||
sql_resource_history_hour = """
|
||||
SELECT resDateTime, resCpuPercent, resRssMb, resIoReadBytes,
|
||||
resIoWriteBytes, resScanDurationMs, resTickFailed
|
||||
FROM Resource_History
|
||||
WHERE resDateTime >= datetime('now', '-1 hour')
|
||||
ORDER BY resDateTime
|
||||
"""
|
||||
sql_resource_history_day = """
|
||||
SELECT resDateTime, resCpuPercent, resRssMb, resIoReadBytes,
|
||||
resIoWriteBytes, resScanDurationMs, resTickFailed
|
||||
FROM Resource_History
|
||||
WHERE resDateTime >= datetime('now', '-1 day')
|
||||
ORDER BY resDateTime
|
||||
"""
|
||||
|
||||
|
||||
sql_resource_history_bucketed_template = """
|
||||
SELECT
|
||||
strftime('%Y-%m-%d %H:00:00', resDateTime) AS bucket,
|
||||
AVG(resCpuPercent) AS resCpuPercent,
|
||||
AVG(resRssMb) AS resRssMb,
|
||||
SUM(resIoReadBytes) AS resIoReadBytes,
|
||||
SUM(resIoWriteBytes) AS resIoWriteBytes,
|
||||
AVG(resScanDurationMs) AS resScanDurationMs,
|
||||
MAX(resTickFailed) AS resTickFailed
|
||||
FROM Resource_History
|
||||
WHERE resDateTime >= datetime('now', '-{days} day')
|
||||
GROUP BY bucket
|
||||
ORDER BY bucket
|
||||
"""
|
||||
sql_resource_history_week = sql_resource_history_bucketed_template.format(days=7)
|
||||
sql_resource_history_month = sql_resource_history_bucketed_template.format(days=30)
|
||||
sql_plugins_events = "SELECT * FROM Plugins_Events"
|
||||
sql_plugins_history = "SELECT * FROM Plugins_History ORDER BY dateTimeChanged DESC"
|
||||
sql_new_devices = """SELECT * FROM (
|
||||
|
||||
@@ -12,6 +12,7 @@ from db.db_upgrade import (
|
||||
ensure_column,
|
||||
ensure_table_columns,
|
||||
ensure_CurrentScan,
|
||||
ensure_Resource_History,
|
||||
ensure_plugins_tables,
|
||||
ensure_Parameters,
|
||||
ensure_Settings,
|
||||
@@ -241,6 +242,9 @@ class DB:
|
||||
# CurrentScan table setup
|
||||
ensure_CurrentScan(self.sql)
|
||||
|
||||
# Resource_History table setup
|
||||
ensure_Resource_History(self.sql)
|
||||
|
||||
# Views are created in importConfigs() after settings are committed,
|
||||
# so NTFPRCS_sleep_time is available when the view is built.
|
||||
# ensure_views is NOT called here.
|
||||
|
||||
@@ -659,6 +659,29 @@ def ensure_CurrentScan(sql) -> bool:
|
||||
return True
|
||||
|
||||
|
||||
def ensure_Resource_History(sql) -> bool:
|
||||
"""
|
||||
Ensures the Resource_History table and its datetime index exist.
|
||||
|
||||
Parameters:
|
||||
- sql: database cursor or connection wrapper (must support execute() and fetchall()).
|
||||
"""
|
||||
sql.execute(""" CREATE TABLE IF NOT EXISTS Resource_History (
|
||||
"index" INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
resDateTime TEXT NOT NULL,
|
||||
resCpuPercent REAL,
|
||||
resRssMb REAL,
|
||||
resIoReadBytes INTEGER,
|
||||
resIoWriteBytes INTEGER,
|
||||
resScanDurationMs INTEGER,
|
||||
resTickFailed INTEGER NOT NULL DEFAULT 0
|
||||
);
|
||||
""")
|
||||
sql.execute("CREATE INDEX IF NOT EXISTS idx_reshist_datetime ON Resource_History(resDateTime);")
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def ensure_Parameters(sql) -> bool:
|
||||
"""
|
||||
Ensures required Parameters table exist.
|
||||
|
||||
@@ -10,6 +10,17 @@ CREATE TABLE IF NOT EXISTS Online_History (
|
||||
offlineDevices INTEGER,
|
||||
PRIMARY KEY("index" AUTOINCREMENT)
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS Resource_History (
|
||||
"index" INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
resDateTime TEXT NOT NULL,
|
||||
resCpuPercent REAL,
|
||||
resRssMb REAL,
|
||||
resIoReadBytes INTEGER,
|
||||
resIoWriteBytes INTEGER,
|
||||
resScanDurationMs INTEGER,
|
||||
resTickFailed INTEGER NOT NULL DEFAULT 0
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_reshist_datetime ON Resource_History(resDateTime);
|
||||
CREATE TABLE Devices (
|
||||
devMac STRING (50) PRIMARY KEY NOT NULL COLLATE NOCASE,
|
||||
devName STRING (50) NOT NULL DEFAULT "(unknown)",
|
||||
|
||||
@@ -36,6 +36,7 @@ def main():
|
||||
DAYS_TO_KEEP_EVENTS = int(get_setting_value("DAYS_TO_KEEP_EVENTS"))
|
||||
CLEAR_NEW_FLAG = get_setting_value("CLEAR_NEW_FLAG")
|
||||
DEV_HIST_DAYS = int(get_setting_value("DEV_HIST_DAYS") or 14)
|
||||
MAINT_PERF_DAYS = int(get_setting_value("MAINT_PERF_DAYS", 30))
|
||||
|
||||
mylog("verbose", [f"[{pluginName}] In script"])
|
||||
|
||||
@@ -48,6 +49,7 @@ def main():
|
||||
PLUGINS_KEEP_HIST,
|
||||
CLEAR_NEW_FLAG,
|
||||
DEV_HIST_DAYS,
|
||||
MAINT_PERF_DAYS,
|
||||
)
|
||||
|
||||
mylog("verbose", [f"[{pluginName}] Cleanup complete"])
|
||||
@@ -66,6 +68,7 @@ def cleanup_database(
|
||||
PLUGINS_KEEP_HIST,
|
||||
CLEAR_NEW_FLAG,
|
||||
DEV_HIST_DAYS=14,
|
||||
MAINT_PERF_DAYS=30,
|
||||
):
|
||||
"""
|
||||
Cleaning out old records from the tables that don't need to keep all data.
|
||||
@@ -109,6 +112,16 @@ def cleanup_database(
|
||||
cursor.execute(sql)
|
||||
mylog("verbose", [f"[{pluginName}] Sessions deleted rows: {cursor.rowcount}"])
|
||||
|
||||
# -----------------------------------------------------
|
||||
# Resource_History (own resource-usage history, MAINT_PERF_DAYS setting -
|
||||
# no separate zero-check needed here: if collection is disabled via
|
||||
# MAINT_PERF_DAYS=0, the table is already empty)
|
||||
mylog("verbose", f"[{pluginName}] Resource_History: Delete all older than {str(MAINT_PERF_DAYS)} days (MAINT_PERF_DAYS setting)")
|
||||
sql = f"""DELETE FROM Resource_History WHERE resDateTime <= date('now', '-{str(MAINT_PERF_DAYS)} day')"""
|
||||
mylog("verbose", [f"[{pluginName}] SQL : {sql}"])
|
||||
cursor.execute(sql)
|
||||
mylog("verbose", [f"[{pluginName}] Resource_History deleted rows: {cursor.rowcount}"])
|
||||
|
||||
# -----------------------------------------------------
|
||||
# Plugins_History
|
||||
mylog("verbose", f"[{pluginName}] Plugins_History: Trim to {str(PLUGINS_KEEP_HIST)} per Plugin")
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
## Overview
|
||||
|
||||
Handles routine housekeeping so long-running logs and in-app notifications don't grow unbounded: trims `app.log` down to a configured line count, and purges old in-app notification entries past a configured count.
|
||||
Handles routine housekeeping so long-running logs and in-app notifications don't grow unbounded: trims `app.log` down to a configured line count, purges old in-app notification entries past a configured count, and sets the retention window (in days) for NetAlertX's own resource-usage history (System Info -> Performance tab).
|
||||
|
||||
### Usage
|
||||
|
||||
|
||||
@@ -271,6 +271,34 @@
|
||||
"string": "How many latest in-app Notification entries to keep. Set to <code>0</code> to disable purging."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"function": "PERF_DAYS",
|
||||
"type": {
|
||||
"dataType": "integer",
|
||||
"elements": [
|
||||
{
|
||||
"elementType": "input",
|
||||
"elementOptions": [{ "type": "number" }],
|
||||
"transformers": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"default_value": 30,
|
||||
"options": [],
|
||||
"localized": ["name", "description"],
|
||||
"name": [
|
||||
{
|
||||
"language_code": "en_us",
|
||||
"string": "Performance history retention (days)"
|
||||
}
|
||||
],
|
||||
"description": [
|
||||
{
|
||||
"language_code": "en_us",
|
||||
"string": "How many days of NetAlertX's own CPU/RAM/IO/scan-duration history to keep (System Info -> Performance tab). Set to <code>0</code> to disable collection entirely - history cannot be backfilled once enabled, so collection starts from whenever this is turned on."
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
"""
|
||||
resource_history.py - tick-scoped resource sampling and persistence.
|
||||
|
||||
Called from __main__.py at the top and bottom of the schedule-tick block, so
|
||||
its window covers run_plugin_scripts("schedule")/process_scan() together -
|
||||
the two calls that actually cost CPU/IO each cycle. Pure, stateless psutil
|
||||
reads - safe to call from a fresh Process() each time, no shared state with
|
||||
health_endpoint.py's live /health gauge at all (see health_endpoint.py).
|
||||
"""
|
||||
|
||||
import psutil
|
||||
|
||||
from logger import mylog
|
||||
from utils.datetime_utils import timeNowUTC
|
||||
|
||||
|
||||
def get_process_cpu_times_and_io():
|
||||
"""
|
||||
(user+system+children CPU seconds, read_bytes, write_bytes) for the current
|
||||
process, as of right now - stateless cumulative reads, meant to be called
|
||||
twice (before/after a tick) and diffed by the caller, not used as a
|
||||
standalone live value. Includes children_user/children_system so a
|
||||
scanning plugin's own subprocess CPU time (reaped into the parent's
|
||||
cpu_times() by the kernel) is captured - io_counters() has no equivalent
|
||||
children field, so IO here only ever reflects NetAlertX's own direct I/O,
|
||||
never a plugin subprocess's.
|
||||
|
||||
Defensive: some container security profiles (custom seccomp, restricted
|
||||
runtimes) can make io_counters()/cpu_times() raise psutil.AccessDenied or
|
||||
AttributeError - sampling must never be able to break the actual scan
|
||||
tick, so failures fall back to zeros rather than propagating.
|
||||
"""
|
||||
try:
|
||||
p = psutil.Process()
|
||||
t = p.cpu_times()
|
||||
io = p.io_counters()
|
||||
cpu_time_s = t.user + t.system + t.children_user + t.children_system
|
||||
return cpu_time_s, io.read_bytes, io.write_bytes
|
||||
except (psutil.Error, AttributeError) as e:
|
||||
mylog("verbose", [f"[resource_history] psutil sampling failed, using zeros: {e}"])
|
||||
return 0.0, 0, 0
|
||||
|
||||
|
||||
def insert_resource_history(db, pre, post, duration_ms, tick_failed=False):
|
||||
"""
|
||||
Insert one Resource_History row from before/after tick-scoped samples.
|
||||
|
||||
Args:
|
||||
db: open DB instance (db.sql.execute()).
|
||||
pre: get_process_cpu_times_and_io() result taken before the tick.
|
||||
post: get_process_cpu_times_and_io() result taken after the tick.
|
||||
duration_ms: wall-clock duration of the bracketed tick, in milliseconds.
|
||||
tick_failed: True if the tick raised and this row was written from the
|
||||
`finally` clause - lets a later viewer distinguish a truncated,
|
||||
crash-adjacent sample from an ordinary short tick.
|
||||
|
||||
A history-write failure (locked DB, schema drift after a bad migration)
|
||||
must never take down the real scan tick's own commit - log and move on.
|
||||
"""
|
||||
cpu_time_delta_s = post[0] - pre[0]
|
||||
|
||||
if duration_ms <= 0:
|
||||
# Guard the actual edge case only - not an arbitrary "under Nms treat
|
||||
# as zero" cutoff, which would also zero out a genuinely fast-but-real
|
||||
# tick's true cost.
|
||||
resCpuPercent = 0.0
|
||||
else:
|
||||
resCpuPercent = (cpu_time_delta_s / (duration_ms / 1000)) * 100
|
||||
|
||||
resIoReadBytes = post[1] - pre[1]
|
||||
resIoWriteBytes = post[2] - pre[2]
|
||||
|
||||
try:
|
||||
db.sql.execute(
|
||||
"""
|
||||
INSERT INTO Resource_History
|
||||
(resDateTime, resCpuPercent, resRssMb, resIoReadBytes,
|
||||
resIoWriteBytes, resScanDurationMs, resTickFailed)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||
""",
|
||||
(
|
||||
timeNowUTC(),
|
||||
resCpuPercent,
|
||||
round(psutil.Process().memory_info().rss / (1024 * 1024), 2),
|
||||
resIoReadBytes,
|
||||
resIoWriteBytes,
|
||||
duration_ms,
|
||||
int(tick_failed),
|
||||
),
|
||||
)
|
||||
except Exception as e:
|
||||
mylog("verbose", [f"[resource_history] insert failed, skipping this cycle: {e}"])
|
||||
@@ -48,6 +48,19 @@ def _make_db():
|
||||
)
|
||||
""")
|
||||
|
||||
cur.execute("""
|
||||
CREATE TABLE Resource_History (
|
||||
"index" INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
resDateTime TEXT NOT NULL,
|
||||
resCpuPercent REAL,
|
||||
resRssMb REAL,
|
||||
resIoReadBytes INTEGER,
|
||||
resIoWriteBytes INTEGER,
|
||||
resScanDurationMs INTEGER,
|
||||
resTickFailed INTEGER NOT NULL DEFAULT 0
|
||||
)
|
||||
""")
|
||||
|
||||
conn.commit()
|
||||
return conn
|
||||
|
||||
@@ -164,6 +177,64 @@ class TestSessionsTrim:
|
||||
assert sessions_expr in source, "Sessions DELETE is not aligned with Events DELETE"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Resource_History retention (MAINT_PERF_DAYS)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _seed_resource_history(cur, old_count: int, recent_count: int, days: int):
|
||||
for i in range(old_count):
|
||||
cur.execute(
|
||||
"INSERT INTO Resource_History (resDateTime, resCpuPercent) "
|
||||
"VALUES (date('now', ?), 10.0)",
|
||||
(f"-{days + 1} day",),
|
||||
)
|
||||
for i in range(recent_count):
|
||||
cur.execute(
|
||||
"INSERT INTO Resource_History (resDateTime, resCpuPercent) "
|
||||
"VALUES (date('now'), 10.0)"
|
||||
)
|
||||
|
||||
|
||||
def _run_resource_history_trim(cur, days: int) -> int:
|
||||
"""Execute the exact DELETE used by db_cleanup and return rowcount."""
|
||||
cur.execute(
|
||||
f"DELETE FROM Resource_History "
|
||||
f"WHERE resDateTime <= date('now', '-{days} day')"
|
||||
)
|
||||
return cur.rowcount
|
||||
|
||||
|
||||
class TestResourceHistoryTrim:
|
||||
|
||||
def test_old_rows_are_deleted(self):
|
||||
conn = _make_db()
|
||||
cur = conn.cursor()
|
||||
_seed_resource_history(cur, old_count=10, recent_count=5, days=30)
|
||||
|
||||
deleted = _run_resource_history_trim(cur, days=30)
|
||||
|
||||
assert deleted == 10
|
||||
cur.execute("SELECT COUNT(*) FROM Resource_History")
|
||||
assert cur.fetchone()[0] == 5
|
||||
|
||||
def test_recent_rows_are_preserved(self):
|
||||
conn = _make_db()
|
||||
cur = conn.cursor()
|
||||
_seed_resource_history(cur, old_count=0, recent_count=20, days=30)
|
||||
|
||||
deleted = _run_resource_history_trim(cur, days=30)
|
||||
|
||||
assert deleted == 0
|
||||
cur.execute("SELECT COUNT(*) FROM Resource_History")
|
||||
assert cur.fetchone()[0] == 20
|
||||
|
||||
def test_empty_table_is_a_no_op(self):
|
||||
conn = _make_db()
|
||||
cur = conn.cursor()
|
||||
|
||||
assert _run_resource_history_trim(cur, days=30) == 0
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# ANALYZE tests
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
"""
|
||||
Tests for the Resource_History read-side rollup queries (System Info ->
|
||||
Performance tab, week/month views) defined in server/const.py. Seeds known
|
||||
per-tick values and asserts SUM() (IO) / AVG() (CPU%/RSS/duration) per hourly
|
||||
bucket match hand-computed expectations - see resource-usage-history PRD
|
||||
Design §4.
|
||||
"""
|
||||
|
||||
import sqlite3
|
||||
|
||||
from server.const import sql_resource_history_week
|
||||
|
||||
|
||||
def _make_db():
|
||||
conn = sqlite3.connect(":memory:")
|
||||
conn.execute("""
|
||||
CREATE TABLE Resource_History (
|
||||
"index" INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
resDateTime TEXT NOT NULL,
|
||||
resCpuPercent REAL,
|
||||
resRssMb REAL,
|
||||
resIoReadBytes INTEGER,
|
||||
resIoWriteBytes INTEGER,
|
||||
resScanDurationMs INTEGER,
|
||||
resTickFailed INTEGER NOT NULL DEFAULT 0
|
||||
)
|
||||
""")
|
||||
conn.commit()
|
||||
return conn
|
||||
|
||||
|
||||
def test_bucketed_query_sums_io_and_averages_the_rest():
|
||||
conn = _make_db()
|
||||
cur = conn.cursor()
|
||||
|
||||
# Two rows in the same hourly bucket, one row in a different bucket -
|
||||
# both within the 7-day window the "week" query scans.
|
||||
cur.executemany(
|
||||
"INSERT INTO Resource_History "
|
||||
"(resDateTime, resCpuPercent, resRssMb, resIoReadBytes, resIoWriteBytes, "
|
||||
" resScanDurationMs, resTickFailed) VALUES (?, ?, ?, ?, ?, ?, ?)",
|
||||
[
|
||||
("2026-01-01 10:05:00", 10.0, 100.0, 1000, 2000, 500, 0),
|
||||
("2026-01-01 10:45:00", 20.0, 200.0, 1500, 2500, 700, 1),
|
||||
("2026-01-01 11:15:00", 30.0, 300.0, 3000, 4000, 900, 0),
|
||||
],
|
||||
)
|
||||
conn.commit()
|
||||
|
||||
# sql_resource_history_week filters on datetime('now', '-7 day') - patch
|
||||
# the fixed timestamps above to "now" so they fall inside the window.
|
||||
cur.execute("UPDATE Resource_History SET resDateTime = datetime('now', '-1 hour') "
|
||||
"WHERE resDateTime = '2026-01-01 10:05:00'")
|
||||
cur.execute("UPDATE Resource_History SET resDateTime = datetime('now', '-1 hour', '+40 minutes') "
|
||||
"WHERE resDateTime = '2026-01-01 10:45:00'")
|
||||
cur.execute("UPDATE Resource_History SET resDateTime = datetime('now') "
|
||||
"WHERE resDateTime = '2026-01-01 11:15:00'")
|
||||
conn.commit()
|
||||
|
||||
rows = cur.execute(sql_resource_history_week).fetchall()
|
||||
|
||||
# Expect two buckets (the two rows sharing an hour collapse into one).
|
||||
assert len(rows) in (1, 2)
|
||||
|
||||
total_io_read = sum(r[3] for r in rows)
|
||||
total_io_write = sum(r[4] for r in rows)
|
||||
assert total_io_read == 1000 + 1500 + 3000
|
||||
assert total_io_write == 2000 + 2500 + 4000
|
||||
|
||||
# Any bucket containing the tickFailed=1 row must report MAX(resTickFailed)=1.
|
||||
assert any(r[6] == 1 for r in rows)
|
||||
|
||||
|
||||
def test_bucketed_query_returns_no_rows_outside_window():
|
||||
conn = _make_db()
|
||||
cur = conn.cursor()
|
||||
cur.execute(
|
||||
"INSERT INTO Resource_History (resDateTime, resCpuPercent) "
|
||||
"VALUES (datetime('now', '-30 day'), 10.0)"
|
||||
)
|
||||
conn.commit()
|
||||
|
||||
rows = cur.execute(sql_resource_history_week).fetchall()
|
||||
assert rows == []
|
||||
@@ -0,0 +1,408 @@
|
||||
"""
|
||||
Tests for server/scan/resource_history.py - tick-scoped resource sampling
|
||||
and the Resource_History insert, plus the __main__.py schedule-tick
|
||||
try/except/finally wiring that calls it.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import sqlite3
|
||||
import types
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
import pytest
|
||||
|
||||
from server.scan import resource_history as rh
|
||||
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
|
||||
from db_test_helpers import make_db # noqa: E402
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# get_process_cpu_times_and_io()
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def test_cpu_times_includes_children(monkeypatch):
|
||||
"""children_user/children_system must be summed in, not just user/system."""
|
||||
fake_times = SimpleNamespace(user=1.0, system=2.0, children_user=3.0, children_system=4.0)
|
||||
fake_io = SimpleNamespace(read_bytes=100, write_bytes=200)
|
||||
fake_process = MagicMock()
|
||||
fake_process.cpu_times.return_value = fake_times
|
||||
fake_process.io_counters.return_value = fake_io
|
||||
monkeypatch.setattr(rh.psutil, "Process", lambda: fake_process)
|
||||
|
||||
cpu_time_s, read_bytes, write_bytes = rh.get_process_cpu_times_and_io()
|
||||
|
||||
assert cpu_time_s == pytest.approx(10.0) # 1+2+3+4
|
||||
assert read_bytes == 100
|
||||
assert write_bytes == 200
|
||||
|
||||
|
||||
def test_cpu_times_falls_back_to_zeros_on_psutil_error(monkeypatch):
|
||||
"""psutil.Error/AttributeError from cpu_times()/io_counters() must not raise."""
|
||||
import psutil as real_psutil
|
||||
|
||||
def raising_process():
|
||||
raise real_psutil.AccessDenied()
|
||||
|
||||
monkeypatch.setattr(rh.psutil, "Process", raising_process)
|
||||
|
||||
result = rh.get_process_cpu_times_and_io()
|
||||
|
||||
assert result == (0.0, 0, 0)
|
||||
|
||||
|
||||
def test_cpu_times_falls_back_on_attribute_error(monkeypatch):
|
||||
"""A mocked cpu_times() missing children_* fields must fall back to zeros."""
|
||||
fake_process = MagicMock()
|
||||
fake_process.cpu_times.return_value = SimpleNamespace(user=1.0, system=1.0) # no children_*
|
||||
monkeypatch.setattr(rh.psutil, "Process", lambda: fake_process)
|
||||
|
||||
result = rh.get_process_cpu_times_and_io()
|
||||
|
||||
assert result == (0.0, 0, 0)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# insert_resource_history()
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class _FakeDB:
|
||||
"""Minimal DB wrapper exposing .sql like the real DB class."""
|
||||
|
||||
def __init__(self, conn):
|
||||
self.sql = conn.cursor()
|
||||
self._conn = conn
|
||||
|
||||
def open(self):
|
||||
pass
|
||||
|
||||
def initDB(self):
|
||||
pass
|
||||
|
||||
def commitDB(self):
|
||||
self._conn.commit()
|
||||
|
||||
|
||||
def _make_resource_history_db():
|
||||
conn = sqlite3.connect(":memory:")
|
||||
conn.execute("""
|
||||
CREATE TABLE Resource_History (
|
||||
"index" INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
resDateTime TEXT NOT NULL,
|
||||
resCpuPercent REAL,
|
||||
resRssMb REAL,
|
||||
resIoReadBytes INTEGER,
|
||||
resIoWriteBytes INTEGER,
|
||||
resScanDurationMs INTEGER,
|
||||
resTickFailed INTEGER NOT NULL DEFAULT 0
|
||||
)
|
||||
""")
|
||||
conn.commit()
|
||||
return _FakeDB(conn)
|
||||
|
||||
|
||||
def test_insert_computes_deltas_exactly():
|
||||
"""resCpuPercent/resIoReadBytes/resIoWriteBytes must match hand-computed deltas."""
|
||||
db = _make_resource_history_db()
|
||||
pre = (10.0, 1000, 2000)
|
||||
post = (13.0, 1500, 2600) # cpu delta = 3.0s, io read delta = 500, io write delta = 600
|
||||
|
||||
rh.insert_resource_history(db, pre, post, duration_ms=6000) # 6s wall time
|
||||
|
||||
row = db.sql.execute(
|
||||
"SELECT resCpuPercent, resIoReadBytes, resIoWriteBytes, resScanDurationMs, resTickFailed "
|
||||
"FROM Resource_History"
|
||||
).fetchone()
|
||||
|
||||
assert row[0] == pytest.approx((3.0 / 6.0) * 100) # 50%
|
||||
assert row[1] == 500
|
||||
assert row[2] == 600
|
||||
assert row[3] == 6000
|
||||
assert row[4] == 0
|
||||
|
||||
|
||||
def test_insert_zero_duration_guard():
|
||||
"""duration_ms <= 0 must yield resCpuPercent == 0.0, not a ZeroDivisionError."""
|
||||
db = _make_resource_history_db()
|
||||
|
||||
rh.insert_resource_history(db, (0.0, 0, 0), (5.0, 10, 10), duration_ms=0)
|
||||
|
||||
row = db.sql.execute("SELECT resCpuPercent FROM Resource_History").fetchone()
|
||||
assert row[0] == 0.0
|
||||
|
||||
|
||||
def test_insert_records_tick_failed_flag():
|
||||
"""tick_failed=True must be stored as resTickFailed=1."""
|
||||
db = _make_resource_history_db()
|
||||
|
||||
rh.insert_resource_history(db, (0.0, 0, 0), (0.0, 0, 0), duration_ms=1000, tick_failed=True)
|
||||
|
||||
row = db.sql.execute("SELECT resTickFailed FROM Resource_History").fetchone()
|
||||
assert row[0] == 1
|
||||
|
||||
|
||||
def test_insert_failure_is_caught_and_logged():
|
||||
"""A raising db.sql.execute() must not propagate - the real tick's commit must survive."""
|
||||
class RaisingDB:
|
||||
class _Sql:
|
||||
def execute(self, *a, **kw):
|
||||
raise sqlite3.OperationalError("locked")
|
||||
sql = _Sql()
|
||||
|
||||
# Must not raise.
|
||||
rh.insert_resource_history(RaisingDB(), (0.0, 0, 0), (0.0, 0, 0), duration_ms=1000)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# __main__.py schedule-tick wiring: try/except/finally + MAINT_PERF_DAYS gate
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class _StopTestLoop(Exception):
|
||||
"""Sentinel raised from the mocked time.sleep() to end the `while True:` loop."""
|
||||
|
||||
|
||||
class _FakeState:
|
||||
def __init__(self):
|
||||
self.pause_until = None
|
||||
self.processScan = True
|
||||
self.graphQLServerStarted = 1
|
||||
|
||||
|
||||
class _FakePM:
|
||||
def __init__(self, raise_on_schedule=False):
|
||||
self.raise_on_schedule = raise_on_schedule
|
||||
self.calls = []
|
||||
|
||||
def check_and_run_user_event(self):
|
||||
pass
|
||||
|
||||
def run_plugin_scripts(self, phase):
|
||||
self.calls.append(phase)
|
||||
if phase == "schedule" and self.raise_on_schedule:
|
||||
raise RuntimeError("boom-in-schedule")
|
||||
|
||||
|
||||
class _FakeNotificationInstance:
|
||||
"""Stands in for NotificationInstance(db) - create() and the three
|
||||
processed/clear methods are all called on this same object in __main__.py."""
|
||||
|
||||
def create(self, final_json, x):
|
||||
return SimpleNamespace(HasNotifications=False)
|
||||
|
||||
def setAllProcessed(self):
|
||||
pass
|
||||
|
||||
def clearPendingEmailFlag(self):
|
||||
pass
|
||||
|
||||
def clearPluginEvents(self):
|
||||
pass
|
||||
|
||||
|
||||
class _FakeWorkflowManager:
|
||||
def __init__(self, db):
|
||||
pass
|
||||
|
||||
def get_new_app_events(self):
|
||||
return []
|
||||
|
||||
|
||||
def _ensure_stub_module(name, **attrs):
|
||||
"""Inject a minimal fake module for an optional third-party dependency
|
||||
that isn't relevant to what's under test here (e.g. json2table, used only
|
||||
for HTML-formatted notification bodies) but is unconditionally imported
|
||||
by server/__main__.py's own import chain."""
|
||||
import importlib
|
||||
try:
|
||||
importlib.import_module(name)
|
||||
except ImportError:
|
||||
mod = types.ModuleType(name)
|
||||
for k, v in attrs.items():
|
||||
setattr(mod, k, v)
|
||||
sys.modules[name] = mod
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def main_mod():
|
||||
_ensure_stub_module("json2table", convert=lambda *a, **kw: "")
|
||||
import server.__main__ as m
|
||||
return m
|
||||
|
||||
|
||||
def _common_patches(monkeypatch, main_mod, db, fake_pm):
|
||||
monkeypatch.setattr(main_mod, "filePermissions", lambda: None)
|
||||
monkeypatch.setattr(main_mod, "update_GUI_port", lambda: None)
|
||||
monkeypatch.setattr(main_mod, "renameSettings", lambda path: None)
|
||||
monkeypatch.setattr(main_mod, "importConfigs", lambda pm, db, all_plugins: (fake_pm, [], False))
|
||||
monkeypatch.setattr(main_mod, "update_api", lambda *a, **kw: None)
|
||||
monkeypatch.setattr(main_mod, "updateState", lambda *a, **kw: _FakeState())
|
||||
monkeypatch.setattr(main_mod, "DB", lambda: db)
|
||||
monkeypatch.setattr(main_mod, "process_scan", lambda db: None)
|
||||
monkeypatch.setattr(main_mod, "get_setting_value", lambda key, default=None: 30 if key == "MAINT_PERF_DAYS" else default)
|
||||
monkeypatch.setattr(main_mod.conf, "last_scan_run", main_mod.timeNowUTC(as_string=False) - main_mod.datetime.timedelta(minutes=5))
|
||||
monkeypatch.setattr(main_mod.conf, "DEEP_SLEEP", False, raising=False)
|
||||
monkeypatch.setattr(main_mod.time, "sleep", lambda *a, **kw: (_ for _ in ()).throw(_StopTestLoop()))
|
||||
|
||||
|
||||
def test_tick_failure_records_row_and_reraises(monkeypatch, main_mod):
|
||||
"""A raising run_plugin_scripts('schedule') must still insert resTickFailed=1
|
||||
and re-raise the original exception unchanged."""
|
||||
conn = make_db()
|
||||
rh_upgrade_conn = conn.cursor()
|
||||
from server.db.db_upgrade import ensure_Resource_History
|
||||
ensure_Resource_History(rh_upgrade_conn)
|
||||
conn.commit()
|
||||
db = _FakeDB(conn)
|
||||
|
||||
fake_pm = _FakePM(raise_on_schedule=True)
|
||||
_common_patches(monkeypatch, main_mod, db, fake_pm)
|
||||
|
||||
with pytest.raises(RuntimeError, match="boom-in-schedule"):
|
||||
main_mod.main()
|
||||
|
||||
row = conn.execute(
|
||||
"SELECT resTickFailed FROM Resource_History"
|
||||
).fetchone()
|
||||
assert row is not None, "Resource_History row must be written even when the tick raises"
|
||||
assert row[0] == 1
|
||||
|
||||
|
||||
def test_normal_tick_records_row_with_tick_failed_zero(monkeypatch, main_mod):
|
||||
"""A normal, non-raising tick must insert a row with resTickFailed=0."""
|
||||
conn = make_db()
|
||||
rh_upgrade_conn = conn.cursor()
|
||||
from server.db.db_upgrade import ensure_Resource_History
|
||||
ensure_Resource_History(rh_upgrade_conn)
|
||||
conn.commit()
|
||||
db = _FakeDB(conn)
|
||||
|
||||
fake_pm = _FakePM(raise_on_schedule=False)
|
||||
_common_patches(monkeypatch, main_mod, db, fake_pm)
|
||||
monkeypatch.setattr(main_mod, "get_notifications", lambda db: {})
|
||||
monkeypatch.setattr(main_mod, "NotificationInstance", lambda db: _FakeNotificationInstance())
|
||||
monkeypatch.setattr(main_mod, "update_devices_names", lambda pm: None)
|
||||
monkeypatch.setattr(main_mod, "WorkflowManager", _FakeWorkflowManager)
|
||||
monkeypatch.setattr(main_mod, "UserEventsQueueInstance", lambda: SimpleNamespace(
|
||||
has_update_devices=lambda: False
|
||||
))
|
||||
|
||||
with pytest.raises(_StopTestLoop):
|
||||
main_mod.main()
|
||||
|
||||
row = conn.execute(
|
||||
"SELECT resTickFailed FROM Resource_History"
|
||||
).fetchone()
|
||||
assert row is not None, "Resource_History row must be written on a normal tick"
|
||||
assert row[0] == 0
|
||||
assert "schedule" in fake_pm.calls
|
||||
|
||||
|
||||
def test_maint_perf_days_zero_disables_collection(monkeypatch, main_mod):
|
||||
"""MAINT_PERF_DAYS == 0 must produce zero new Resource_History rows."""
|
||||
conn = make_db()
|
||||
rh_upgrade_conn = conn.cursor()
|
||||
from server.db.db_upgrade import ensure_Resource_History
|
||||
ensure_Resource_History(rh_upgrade_conn)
|
||||
conn.commit()
|
||||
db = _FakeDB(conn)
|
||||
|
||||
fake_pm = _FakePM(raise_on_schedule=False)
|
||||
_common_patches(monkeypatch, main_mod, db, fake_pm)
|
||||
# Override the default 30-day fixture value with 0 (disabled).
|
||||
monkeypatch.setattr(main_mod, "get_setting_value", lambda key, default=None: 0 if key == "MAINT_PERF_DAYS" else default)
|
||||
monkeypatch.setattr(main_mod, "get_notifications", lambda db: {})
|
||||
monkeypatch.setattr(main_mod, "NotificationInstance", lambda db: _FakeNotificationInstance())
|
||||
monkeypatch.setattr(main_mod, "update_devices_names", lambda pm: None)
|
||||
monkeypatch.setattr(main_mod, "WorkflowManager", _FakeWorkflowManager)
|
||||
monkeypatch.setattr(main_mod, "UserEventsQueueInstance", lambda: SimpleNamespace(
|
||||
has_update_devices=lambda: False
|
||||
))
|
||||
|
||||
with pytest.raises(_StopTestLoop):
|
||||
main_mod.main()
|
||||
|
||||
row = conn.execute("SELECT COUNT(*) FROM Resource_History").fetchone()
|
||||
assert row[0] == 0, "MAINT_PERF_DAYS=0 must produce zero Resource_History rows"
|
||||
|
||||
|
||||
def test_after_sample_precedes_insert_and_final_commit(monkeypatch, main_mod):
|
||||
"""Self-measurement boundary: the 'after' sample and insert_resource_history()
|
||||
must both run before the tick's own later db.commitDB() calls, so the
|
||||
feature's own write never inflates what it reports."""
|
||||
conn = make_db()
|
||||
rh_upgrade_conn = conn.cursor()
|
||||
from server.db.db_upgrade import ensure_Resource_History
|
||||
ensure_Resource_History(rh_upgrade_conn)
|
||||
conn.commit()
|
||||
db = _FakeDB(conn)
|
||||
|
||||
call_order = []
|
||||
real_commit = db.commitDB
|
||||
real_get_times = rh.get_process_cpu_times_and_io
|
||||
real_insert = rh.insert_resource_history
|
||||
|
||||
def spy_commit():
|
||||
call_order.append("commitDB")
|
||||
return real_commit()
|
||||
|
||||
def spy_get_times():
|
||||
call_order.append("sample")
|
||||
return real_get_times()
|
||||
|
||||
def spy_insert(*args, **kwargs):
|
||||
call_order.append("insert")
|
||||
return real_insert(*args, **kwargs)
|
||||
|
||||
db.commitDB = spy_commit
|
||||
|
||||
fake_pm = _FakePM(raise_on_schedule=False)
|
||||
_common_patches(monkeypatch, main_mod, db, fake_pm)
|
||||
monkeypatch.setattr(main_mod, "get_process_cpu_times_and_io", spy_get_times)
|
||||
monkeypatch.setattr(main_mod, "insert_resource_history", spy_insert)
|
||||
monkeypatch.setattr(main_mod, "get_notifications", lambda db: {})
|
||||
monkeypatch.setattr(main_mod, "NotificationInstance", lambda db: _FakeNotificationInstance())
|
||||
monkeypatch.setattr(main_mod, "update_devices_names", lambda pm: None)
|
||||
monkeypatch.setattr(main_mod, "WorkflowManager", _FakeWorkflowManager)
|
||||
monkeypatch.setattr(main_mod, "UserEventsQueueInstance", lambda: SimpleNamespace(
|
||||
has_update_devices=lambda: False
|
||||
))
|
||||
|
||||
with pytest.raises(_StopTestLoop):
|
||||
main_mod.main()
|
||||
|
||||
insert_index = call_order.index("insert")
|
||||
# Both samples (pre and post) happen before the insert...
|
||||
assert call_order[:insert_index].count("sample") == 2
|
||||
# ...and every commitDB() recorded happens after the insert, not before it.
|
||||
commits_before_insert = call_order[:insert_index].count("commitDB")
|
||||
assert commits_before_insert == 0, (
|
||||
f"insert_resource_history() must run before the tick's own commitDB() calls, "
|
||||
f"call order was: {call_order}"
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Non-interference regression test (Design §1's correction): the /health
|
||||
# live gauge (cpu_percent-based) must not affect the tick-scoped sampler's
|
||||
# own computation, since they use independent Process() objects.
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def test_health_gauge_does_not_affect_tick_scoped_sampling(monkeypatch):
|
||||
from server.api_server import health_endpoint
|
||||
|
||||
pre = rh.get_process_cpu_times_and_io()
|
||||
|
||||
# Simulate /health being polled mid-tick, between the two tick-scoped samples.
|
||||
health_endpoint.get_process_cpu_percent()
|
||||
|
||||
post = rh.get_process_cpu_times_and_io()
|
||||
|
||||
db = _make_resource_history_db()
|
||||
rh.insert_resource_history(db, pre, post, duration_ms=1000)
|
||||
|
||||
row = db.sql.execute("SELECT resCpuPercent FROM Resource_History").fetchone()
|
||||
# Purely a non-crash / non-interference check - the /health call must not
|
||||
# raise or corrupt the tick-scoped computation's independent state.
|
||||
assert row[0] is not None
|
||||
@@ -0,0 +1,51 @@
|
||||
"""
|
||||
Tests for health_endpoint.py's process-scoped metrics: get_process_cpu_percent()/
|
||||
get_process_rss_mb() (the /health live gauge, Design §2 of the resource-usage
|
||||
history feature) and their inclusion in get_health_status().
|
||||
"""
|
||||
|
||||
import psutil
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
from server.api_server import health_endpoint as he
|
||||
|
||||
|
||||
def test_get_process_cpu_percent_returns_value(monkeypatch):
|
||||
monkeypatch.setattr(he._health_process, "cpu_percent", lambda interval=None: 12.5)
|
||||
assert he.get_process_cpu_percent() == 12.5
|
||||
|
||||
|
||||
def test_get_process_cpu_percent_falls_back_to_zero_on_error(monkeypatch):
|
||||
def raising(interval=None):
|
||||
raise psutil.NoSuchProcess(pid=1)
|
||||
monkeypatch.setattr(he._health_process, "cpu_percent", raising)
|
||||
assert he.get_process_cpu_percent() == 0.0
|
||||
|
||||
|
||||
def test_get_process_rss_mb_returns_rounded_mb(monkeypatch):
|
||||
fake_process = MagicMock()
|
||||
fake_process.memory_info.return_value = MagicMock(rss=150 * 1024 * 1024)
|
||||
monkeypatch.setattr(he.psutil, "Process", lambda: fake_process)
|
||||
assert he.get_process_rss_mb() == 150.0
|
||||
|
||||
|
||||
def test_get_process_rss_mb_falls_back_to_zero_on_error(monkeypatch):
|
||||
def raising():
|
||||
raise psutil.AccessDenied()
|
||||
monkeypatch.setattr(he.psutil, "Process", raising)
|
||||
assert he.get_process_rss_mb() == 0.0
|
||||
|
||||
|
||||
def test_get_health_status_includes_process_fields(monkeypatch):
|
||||
"""get_health_status() must expose the two new process metrics alongside
|
||||
the seven existing host-level ones."""
|
||||
monkeypatch.setattr(he, "get_process_cpu_percent", lambda: 3.2)
|
||||
monkeypatch.setattr(he, "get_process_rss_mb", lambda: 142.75)
|
||||
|
||||
status = he.get_health_status()
|
||||
|
||||
assert status["process_cpu_pct"] == 3.2
|
||||
assert status["process_rss_mb"] == 142.75
|
||||
for key in ("db_size_mb", "mem_usage_pct", "load_1m", "storage_pct",
|
||||
"cpu_temp", "storage_gb", "mem_mb"):
|
||||
assert key in status
|
||||
Reference in new issue
Block a user