mirror of
https://github.com/jokob-sk/NetAlertX.git
synced 2026-04-01 07:42:19 -07:00
Compare commits
24 Commits
feat-proxm
...
v26.3.7
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1d1a8045a0 | ||
|
|
f8c09d35a7 | ||
|
|
d8d090404e | ||
|
|
5a6de6d832 | ||
|
|
05b63cb730 | ||
|
|
2921614eac | ||
|
|
17d95d802f | ||
|
|
a0048980b8 | ||
|
|
89811cd133 | ||
|
|
b854206599 | ||
|
|
a532c98115 | ||
|
|
da23880eb1 | ||
|
|
c73ce839f2 | ||
|
|
5c0f29b97c | ||
|
|
f1496b483b | ||
|
|
ba26f34191 | ||
|
|
37f8a44cb3 | ||
|
|
76a259d9e5 | ||
|
|
8ab9d9f395 | ||
|
|
c1d53ff93f | ||
|
|
a329c5b541 | ||
|
|
0555105473 | ||
|
|
b0aa5d0e45 | ||
|
|
93df52f70c |
@@ -19,6 +19,9 @@ services:
|
||||
- CHOWN # Required for root-entrypoint to chown /data + /tmp before dropping privileges
|
||||
- SETUID # Required for root-entrypoint to switch to non-root user
|
||||
- SETGID # Required for root-entrypoint to switch to non-root group
|
||||
sysctls: # ARP flux mitigation for host networking accuracy
|
||||
net.ipv4.conf.all.arp_ignore: 1
|
||||
net.ipv4.conf.all.arp_announce: 2
|
||||
volumes:
|
||||
|
||||
- type: volume # Persistent Docker-managed Named Volume for storage
|
||||
|
||||
@@ -30,6 +30,9 @@ services:
|
||||
- CHOWN # Required for root-entrypoint to chown /data + /tmp before dropping privileges
|
||||
- SETUID # Required for root-entrypoint to switch to non-root user
|
||||
- SETGID # Required for root-entrypoint to switch to non-root group
|
||||
sysctls: # ARP flux mitigation (reduces duplicate/ambiguous ARP behavior on host networking)
|
||||
net.ipv4.conf.all.arp_ignore: 1
|
||||
net.ipv4.conf.all.arp_announce: 2
|
||||
|
||||
volumes:
|
||||
- type: volume # Persistent Docker-managed named volume for config + database
|
||||
|
||||
51
docs/docker-troubleshooting/arp-flux-sysctls.md
Normal file
51
docs/docker-troubleshooting/arp-flux-sysctls.md
Normal file
@@ -0,0 +1,51 @@
|
||||
# ARP Flux Sysctls Not Set
|
||||
|
||||
## Issue Description
|
||||
|
||||
NetAlertX detected that ARP flux protection sysctls are not set as expected:
|
||||
|
||||
- `net.ipv4.conf.all.arp_ignore=1`
|
||||
- `net.ipv4.conf.all.arp_announce=2`
|
||||
|
||||
## Security Ramifications
|
||||
|
||||
This is not a direct container breakout risk, but detection quality can degrade:
|
||||
|
||||
- Incorrect IP/MAC associations
|
||||
- Device state flapping
|
||||
- Unreliable topology or presence data
|
||||
|
||||
## Why You're Seeing This Issue
|
||||
|
||||
The running environment does not provide the expected kernel sysctl values. This is common in Docker setups where sysctls were not explicitly configured.
|
||||
|
||||
## How to Correct the Issue
|
||||
|
||||
Set these sysctls at container runtime.
|
||||
|
||||
- In `docker-compose.yml` (preferred):
|
||||
```yaml
|
||||
services:
|
||||
netalertx:
|
||||
sysctls:
|
||||
net.ipv4.conf.all.arp_ignore: 1
|
||||
net.ipv4.conf.all.arp_announce: 2
|
||||
```
|
||||
|
||||
- For `docker run`:
|
||||
```bash
|
||||
docker run \
|
||||
--sysctl net.ipv4.conf.all.arp_ignore=1 \
|
||||
--sysctl net.ipv4.conf.all.arp_announce=2 \
|
||||
ghcr.io/netalertx/netalertx:latest
|
||||
```
|
||||
|
||||
> **Note:** Setting `net.ipv4.conf.all.arp_ignore` and `net.ipv4.conf.all.arp_announce` may fail with "operation not permitted" unless the container is run with elevated privileges. To resolve this, you can:
|
||||
> - Use `--privileged` with `docker run`.
|
||||
> - Use the more restrictive `--cap-add=NET_ADMIN` (or `cap_add: [NET_ADMIN]` in `docker-compose` service definitions) to allow the sysctls to be applied at runtime.
|
||||
|
||||
## Additional Resources
|
||||
|
||||
For broader Docker Compose guidance, see:
|
||||
|
||||
- [DOCKER_COMPOSE.md](https://docs.netalertx.com/DOCKER_COMPOSE)
|
||||
@@ -53,7 +53,12 @@
|
||||
<div class="col-md-12">
|
||||
<div class="box" id="clients">
|
||||
<div class="box-header ">
|
||||
<h3 class="box-title col-md-12"><?= lang('Device_Shortcut_OnlineChart');?> </h3>
|
||||
<h3 class="box-title"><?= lang('Device_Shortcut_OnlineChart');?> </h3>
|
||||
<div class="box-tools pull-right">
|
||||
<button type="button" class="btn btn-box-tool" data-widget="collapse">
|
||||
<i class="fa fa-minus"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box-body">
|
||||
<div class="chart">
|
||||
@@ -72,10 +77,15 @@
|
||||
<!-- Device Filters ------------------------------------------------------- -->
|
||||
<div class="box box-aqua hidden" id="columnFiltersWrap">
|
||||
<div class="box-header ">
|
||||
<h3 class="box-title col-md-12"><?= lang('Devices_Filters');?> </h3>
|
||||
<h3 class="box-title"><?= lang('Devices_Filters');?> </h3>
|
||||
<div class="box-tools pull-right">
|
||||
<button type="button" class="btn btn-box-tool" data-widget="collapse">
|
||||
<i class="fa fa-minus"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Placeholder ------------------------------------------------------- -->
|
||||
<div id="columnFilters" ></div>
|
||||
<div class="box-body" id="columnFilters"></div>
|
||||
</div>
|
||||
|
||||
<!-- datatable ------------------------------------------------------------- -->
|
||||
@@ -148,6 +158,20 @@
|
||||
// DEVICE_COLUMN_FIELDS, COL, NUMERIC_DEFAULTS, GRAPHQL_EXTRA_FIELDS, COLUMN_NAME_MAP
|
||||
// are all defined in js/device-columns.js — edit that file to add new columns.
|
||||
|
||||
// Collapse DevicePresence and Filters sections by default on small/mobile screens
|
||||
(function collapseOnMobile() {
|
||||
if (window.innerWidth < 768) {
|
||||
['#clients', '#columnFiltersWrap'].forEach(function(sel) {
|
||||
var $box = $(sel);
|
||||
if ($box.length) {
|
||||
$box.addClass('collapsed-box');
|
||||
$box.find('.box-body, .box-footer').hide();
|
||||
$box.find('[data-widget="collapse"] i').removeClass('fa-minus').addClass('fa-plus');
|
||||
}
|
||||
});
|
||||
}
|
||||
})();
|
||||
|
||||
// Read parameters & Initialize components
|
||||
callAfterAppInitialized(main)
|
||||
showSpinner();
|
||||
@@ -472,12 +496,9 @@ function renderFilters(customData) {
|
||||
// Collect filters
|
||||
const columnFilters = collectFilters();
|
||||
|
||||
// Update DataTable with the new filters or search value (if applicable)
|
||||
$('#tableDevices').DataTable().draw();
|
||||
|
||||
// Optionally, apply column filters (if using filters for individual columns)
|
||||
// Apply column filters then draw once (previously drew twice — bug fixed).
|
||||
const table = $('#tableDevices').DataTable();
|
||||
table.columnFilters = columnFilters; // Apply your column filters logic
|
||||
table.columnFilters = columnFilters;
|
||||
table.draw();
|
||||
});
|
||||
|
||||
@@ -603,6 +624,10 @@ function hasEnabledDeviceScanners() {
|
||||
// Update the title-bar ETA subtitle and the DataTables empty-state message.
|
||||
// Called on every nax:scanEtaUpdate; the inner ticker keeps the title bar live between events.
|
||||
function updateScanEtaDisplay(nextScanTime, currentState) {
|
||||
// Detect scan-finished transition BEFORE updating _currentStateAnchor.
|
||||
// justFinishedScanning is true only when the backend transitions scanning → idle.
|
||||
var justFinishedScanning = (currentState === 'Process: Idle') && isScanningState(_currentStateAnchor);
|
||||
|
||||
// Prefer the backend-computed values; keep previous anchors if not yet received.
|
||||
_nextScanTimeAnchor = nextScanTime || _nextScanTimeAnchor;
|
||||
_currentStateAnchor = currentState || _currentStateAnchor;
|
||||
@@ -636,12 +661,26 @@ function updateScanEtaDisplay(nextScanTime, currentState) {
|
||||
eta.style.display = '';
|
||||
}
|
||||
|
||||
// Update DataTables empty message once per SSE event — avoids AJAX spam on server-side tables.
|
||||
// Update DataTables empty message once per SSE event.
|
||||
// NOTE: Do NOT call dt.draw() here — on page load the SSE queue replays all
|
||||
// accumulated events at once, causing a draw() (= GraphQL AJAX call) per event.
|
||||
// Instead, update the visible empty-state DOM cell directly.
|
||||
var label = getEtaLabel();
|
||||
if ($.fn.DataTable.isDataTable('#tableDevices')) {
|
||||
var dt = $('#tableDevices').DataTable();
|
||||
dt.settings()[0].oLanguage.sEmptyTable = buildEmptyDeviceTableMessage(label);
|
||||
if (dt.page.info().recordsTotal === 0) { dt.draw(false); }
|
||||
var newEmptyMsg = buildEmptyDeviceTableMessage(label);
|
||||
dt.settings()[0].oLanguage.sEmptyTable = newEmptyMsg;
|
||||
if (dt.page.info().recordsTotal === 0) {
|
||||
// Patch the visible cell text without triggering a server-side AJAX reload.
|
||||
$('#tableDevices tbody .dataTables_empty').html(newEmptyMsg);
|
||||
}
|
||||
|
||||
// When scanning just finished and the table is still empty, reload data so
|
||||
// newly discovered devices appear automatically. Skip reload if there are
|
||||
// already rows — no need to disturb the user's current view.
|
||||
if (justFinishedScanning && dt.page.info().recordsTotal === 0) {
|
||||
dt.ajax.reload(null, false); // false = keep current page position
|
||||
}
|
||||
}
|
||||
|
||||
tickTitleBar();
|
||||
|
||||
807
front/php/templates/language/id_id.json
Normal file
807
front/php/templates/language/id_id.json
Normal file
@@ -0,0 +1,807 @@
|
||||
{
|
||||
"API_CUSTOM_SQL_description": "",
|
||||
"API_CUSTOM_SQL_name": "",
|
||||
"API_TOKEN_description": "",
|
||||
"API_TOKEN_name": "",
|
||||
"API_display_name": "",
|
||||
"API_icon": "",
|
||||
"About_Design": "",
|
||||
"About_Exit": "",
|
||||
"About_Title": "",
|
||||
"AppEvents_AppEventProcessed": "",
|
||||
"AppEvents_DateTimeCreated": "",
|
||||
"AppEvents_Extra": "",
|
||||
"AppEvents_GUID": "",
|
||||
"AppEvents_Helper1": "",
|
||||
"AppEvents_Helper2": "",
|
||||
"AppEvents_Helper3": "",
|
||||
"AppEvents_ObjectForeignKey": "",
|
||||
"AppEvents_ObjectIndex": "",
|
||||
"AppEvents_ObjectIsArchived": "",
|
||||
"AppEvents_ObjectIsNew": "",
|
||||
"AppEvents_ObjectPlugin": "",
|
||||
"AppEvents_ObjectPrimaryID": "",
|
||||
"AppEvents_ObjectSecondaryID": "",
|
||||
"AppEvents_ObjectStatus": "",
|
||||
"AppEvents_ObjectStatusColumn": "",
|
||||
"AppEvents_ObjectType": "",
|
||||
"AppEvents_Plugin": "",
|
||||
"AppEvents_Type": "",
|
||||
"BACKEND_API_URL_description": "",
|
||||
"BACKEND_API_URL_name": "",
|
||||
"BackDevDetail_Actions_Ask_Run": "",
|
||||
"BackDevDetail_Actions_Not_Registered": "",
|
||||
"BackDevDetail_Actions_Title_Run": "",
|
||||
"BackDevDetail_Copy_Ask": "",
|
||||
"BackDevDetail_Copy_Title": "",
|
||||
"BackDevDetail_Tools_WOL_error": "",
|
||||
"BackDevDetail_Tools_WOL_okay": "",
|
||||
"BackDevices_Arpscan_disabled": "",
|
||||
"BackDevices_Arpscan_enabled": "",
|
||||
"BackDevices_Backup_CopError": "",
|
||||
"BackDevices_Backup_Failed": "",
|
||||
"BackDevices_Backup_okay": "",
|
||||
"BackDevices_DBTools_DelDevError_a": "",
|
||||
"BackDevices_DBTools_DelDevError_b": "",
|
||||
"BackDevices_DBTools_DelDev_a": "",
|
||||
"BackDevices_DBTools_DelDev_b": "",
|
||||
"BackDevices_DBTools_DelEvents": "",
|
||||
"BackDevices_DBTools_DelEventsError": "",
|
||||
"BackDevices_DBTools_ImportCSV": "",
|
||||
"BackDevices_DBTools_ImportCSVError": "",
|
||||
"BackDevices_DBTools_ImportCSVMissing": "",
|
||||
"BackDevices_DBTools_Purge": "",
|
||||
"BackDevices_DBTools_UpdDev": "",
|
||||
"BackDevices_DBTools_UpdDevError": "",
|
||||
"BackDevices_DBTools_Upgrade": "",
|
||||
"BackDevices_DBTools_UpgradeError": "",
|
||||
"BackDevices_Device_UpdDevError": "",
|
||||
"BackDevices_Restore_CopError": "",
|
||||
"BackDevices_Restore_Failed": "",
|
||||
"BackDevices_Restore_okay": "",
|
||||
"BackDevices_darkmode_disabled": "",
|
||||
"BackDevices_darkmode_enabled": "",
|
||||
"CLEAR_NEW_FLAG_description": "",
|
||||
"CLEAR_NEW_FLAG_name": "",
|
||||
"CustProps_cant_remove": "",
|
||||
"DAYS_TO_KEEP_EVENTS_description": "",
|
||||
"DAYS_TO_KEEP_EVENTS_name": "",
|
||||
"DISCOVER_PLUGINS_description": "",
|
||||
"DISCOVER_PLUGINS_name": "",
|
||||
"DevDetail_Children_Title": "",
|
||||
"DevDetail_Copy_Device_Title": "",
|
||||
"DevDetail_Copy_Device_Tooltip": "",
|
||||
"DevDetail_CustomProperties_Title": "",
|
||||
"DevDetail_CustomProps_reset_info": "",
|
||||
"DevDetail_DisplayFields_Title": "",
|
||||
"DevDetail_EveandAl_AlertAllEvents": "",
|
||||
"DevDetail_EveandAl_AlertDown": "",
|
||||
"DevDetail_EveandAl_Archived": "",
|
||||
"DevDetail_EveandAl_NewDevice": "",
|
||||
"DevDetail_EveandAl_NewDevice_Tooltip": "",
|
||||
"DevDetail_EveandAl_RandomMAC": "",
|
||||
"DevDetail_EveandAl_ScanCycle": "",
|
||||
"DevDetail_EveandAl_ScanCycle_a": "",
|
||||
"DevDetail_EveandAl_ScanCycle_z": "",
|
||||
"DevDetail_EveandAl_Skip": "",
|
||||
"DevDetail_EveandAl_Title": "",
|
||||
"DevDetail_Events_CheckBox": "",
|
||||
"DevDetail_GoToNetworkNode": "",
|
||||
"DevDetail_Icon": "",
|
||||
"DevDetail_Icon_Descr": "",
|
||||
"DevDetail_Loading": "",
|
||||
"DevDetail_MainInfo_Comments": "",
|
||||
"DevDetail_MainInfo_Favorite": "",
|
||||
"DevDetail_MainInfo_Group": "",
|
||||
"DevDetail_MainInfo_Location": "",
|
||||
"DevDetail_MainInfo_Name": "",
|
||||
"DevDetail_MainInfo_Network": "",
|
||||
"DevDetail_MainInfo_Network_Port": "",
|
||||
"DevDetail_MainInfo_Network_Site": "",
|
||||
"DevDetail_MainInfo_Network_Title": "",
|
||||
"DevDetail_MainInfo_Owner": "",
|
||||
"DevDetail_MainInfo_SSID": "",
|
||||
"DevDetail_MainInfo_Title": "",
|
||||
"DevDetail_MainInfo_Type": "",
|
||||
"DevDetail_MainInfo_Vendor": "",
|
||||
"DevDetail_MainInfo_mac": "",
|
||||
"DevDetail_NavToChildNode": "",
|
||||
"DevDetail_Network_Node_hover": "",
|
||||
"DevDetail_Network_Port_hover": "",
|
||||
"DevDetail_Nmap_Scans": "",
|
||||
"DevDetail_Nmap_Scans_desc": "",
|
||||
"DevDetail_Nmap_buttonDefault": "",
|
||||
"DevDetail_Nmap_buttonDefault_text": "",
|
||||
"DevDetail_Nmap_buttonDetail": "",
|
||||
"DevDetail_Nmap_buttonDetail_text": "",
|
||||
"DevDetail_Nmap_buttonFast": "",
|
||||
"DevDetail_Nmap_buttonFast_text": "",
|
||||
"DevDetail_Nmap_buttonSkipDiscovery": "",
|
||||
"DevDetail_Nmap_buttonSkipDiscovery_text": "",
|
||||
"DevDetail_Nmap_resultsLink": "",
|
||||
"DevDetail_Owner_hover": "",
|
||||
"DevDetail_Periodselect_All": "",
|
||||
"DevDetail_Periodselect_LastMonth": "",
|
||||
"DevDetail_Periodselect_LastWeek": "",
|
||||
"DevDetail_Periodselect_LastYear": "",
|
||||
"DevDetail_Periodselect_today": "",
|
||||
"DevDetail_Run_Actions_Title": "",
|
||||
"DevDetail_Run_Actions_Tooltip": "",
|
||||
"DevDetail_SessionInfo_FirstSession": "",
|
||||
"DevDetail_SessionInfo_LastIP": "",
|
||||
"DevDetail_SessionInfo_LastSession": "",
|
||||
"DevDetail_SessionInfo_StaticIP": "",
|
||||
"DevDetail_SessionInfo_Status": "",
|
||||
"DevDetail_SessionInfo_Title": "",
|
||||
"DevDetail_SessionTable_Additionalinfo": "",
|
||||
"DevDetail_SessionTable_Connection": "",
|
||||
"DevDetail_SessionTable_Disconnection": "",
|
||||
"DevDetail_SessionTable_Duration": "",
|
||||
"DevDetail_SessionTable_IP": "",
|
||||
"DevDetail_SessionTable_Order": "",
|
||||
"DevDetail_Shortcut_CurrentStatus": "",
|
||||
"DevDetail_Shortcut_DownAlerts": "",
|
||||
"DevDetail_Shortcut_Presence": "",
|
||||
"DevDetail_Shortcut_Sessions": "",
|
||||
"DevDetail_Tab_Details": "",
|
||||
"DevDetail_Tab_Events": "",
|
||||
"DevDetail_Tab_EventsTableDate": "",
|
||||
"DevDetail_Tab_EventsTableEvent": "",
|
||||
"DevDetail_Tab_EventsTableIP": "",
|
||||
"DevDetail_Tab_EventsTableInfo": "",
|
||||
"DevDetail_Tab_Nmap": "",
|
||||
"DevDetail_Tab_NmapEmpty": "",
|
||||
"DevDetail_Tab_NmapTableExtra": "",
|
||||
"DevDetail_Tab_NmapTableHeader": "",
|
||||
"DevDetail_Tab_NmapTableIndex": "",
|
||||
"DevDetail_Tab_NmapTablePort": "",
|
||||
"DevDetail_Tab_NmapTableService": "",
|
||||
"DevDetail_Tab_NmapTableState": "",
|
||||
"DevDetail_Tab_NmapTableText": "",
|
||||
"DevDetail_Tab_NmapTableTime": "",
|
||||
"DevDetail_Tab_Plugins": "",
|
||||
"DevDetail_Tab_Presence": "",
|
||||
"DevDetail_Tab_Sessions": "",
|
||||
"DevDetail_Tab_Tools": "",
|
||||
"DevDetail_Tab_Tools_Internet_Info_Description": "",
|
||||
"DevDetail_Tab_Tools_Internet_Info_Error": "",
|
||||
"DevDetail_Tab_Tools_Internet_Info_Start": "",
|
||||
"DevDetail_Tab_Tools_Internet_Info_Title": "",
|
||||
"DevDetail_Tab_Tools_Nslookup_Description": "",
|
||||
"DevDetail_Tab_Tools_Nslookup_Error": "",
|
||||
"DevDetail_Tab_Tools_Nslookup_Start": "",
|
||||
"DevDetail_Tab_Tools_Nslookup_Title": "",
|
||||
"DevDetail_Tab_Tools_Speedtest_Description": "",
|
||||
"DevDetail_Tab_Tools_Speedtest_Start": "",
|
||||
"DevDetail_Tab_Tools_Speedtest_Title": "",
|
||||
"DevDetail_Tab_Tools_Traceroute_Description": "",
|
||||
"DevDetail_Tab_Tools_Traceroute_Error": "",
|
||||
"DevDetail_Tab_Tools_Traceroute_Start": "",
|
||||
"DevDetail_Tab_Tools_Traceroute_Title": "",
|
||||
"DevDetail_Tools_WOL": "",
|
||||
"DevDetail_Tools_WOL_noti": "",
|
||||
"DevDetail_Tools_WOL_noti_text": "",
|
||||
"DevDetail_Type_hover": "",
|
||||
"DevDetail_Vendor_hover": "",
|
||||
"DevDetail_WOL_Title": "",
|
||||
"DevDetail_button_AddIcon": "",
|
||||
"DevDetail_button_AddIcon_Help": "",
|
||||
"DevDetail_button_AddIcon_Tooltip": "",
|
||||
"DevDetail_button_Delete": "",
|
||||
"DevDetail_button_DeleteEvents": "",
|
||||
"DevDetail_button_DeleteEvents_Warning": "",
|
||||
"DevDetail_button_Delete_ask": "",
|
||||
"DevDetail_button_OverwriteIcons": "",
|
||||
"DevDetail_button_OverwriteIcons_Tooltip": "",
|
||||
"DevDetail_button_OverwriteIcons_Warning": "",
|
||||
"DevDetail_button_Reset": "",
|
||||
"DevDetail_button_Save": "",
|
||||
"DeviceEdit_ValidMacIp": "",
|
||||
"Device_MultiEdit": "",
|
||||
"Device_MultiEdit_Backup": "",
|
||||
"Device_MultiEdit_Fields": "",
|
||||
"Device_MultiEdit_MassActions": "",
|
||||
"Device_MultiEdit_No_Devices": "",
|
||||
"Device_MultiEdit_Tooltip": "",
|
||||
"Device_NextScan_Imminent": "",
|
||||
"Device_NextScan_In": "",
|
||||
"Device_NoData_Help": "",
|
||||
"Device_NoData_Scanning": "",
|
||||
"Device_NoData_Title": "",
|
||||
"Device_Save_Failed": "",
|
||||
"Device_Save_Unauthorized": "",
|
||||
"Device_Saved_Success": "",
|
||||
"Device_Saved_Unexpected": "",
|
||||
"Device_Scanning": "",
|
||||
"Device_Searchbox": "",
|
||||
"Device_Shortcut_AllDevices": "",
|
||||
"Device_Shortcut_AllNodes": "",
|
||||
"Device_Shortcut_Archived": "",
|
||||
"Device_Shortcut_Connected": "",
|
||||
"Device_Shortcut_Devices": "",
|
||||
"Device_Shortcut_DownAlerts": "",
|
||||
"Device_Shortcut_DownOnly": "",
|
||||
"Device_Shortcut_Favorites": "",
|
||||
"Device_Shortcut_NewDevices": "",
|
||||
"Device_Shortcut_OnlineChart": "",
|
||||
"Device_Shortcut_Unstable": "",
|
||||
"Device_TableHead_AlertDown": "",
|
||||
"Device_TableHead_Connected_Devices": "",
|
||||
"Device_TableHead_CustomProps": "",
|
||||
"Device_TableHead_FQDN": "",
|
||||
"Device_TableHead_Favorite": "",
|
||||
"Device_TableHead_FirstSession": "",
|
||||
"Device_TableHead_Flapping": "",
|
||||
"Device_TableHead_GUID": "",
|
||||
"Device_TableHead_Group": "",
|
||||
"Device_TableHead_IPv4": "",
|
||||
"Device_TableHead_IPv6": "",
|
||||
"Device_TableHead_Icon": "",
|
||||
"Device_TableHead_LastIP": "",
|
||||
"Device_TableHead_LastIPOrder": "",
|
||||
"Device_TableHead_LastSession": "",
|
||||
"Device_TableHead_Location": "",
|
||||
"Device_TableHead_MAC": "",
|
||||
"Device_TableHead_MAC_full": "",
|
||||
"Device_TableHead_Name": "",
|
||||
"Device_TableHead_NetworkSite": "",
|
||||
"Device_TableHead_Owner": "",
|
||||
"Device_TableHead_ParentRelType": "",
|
||||
"Device_TableHead_Parent_MAC": "",
|
||||
"Device_TableHead_Port": "",
|
||||
"Device_TableHead_PresentLastScan": "",
|
||||
"Device_TableHead_ReqNicsOnline": "",
|
||||
"Device_TableHead_RowID": "",
|
||||
"Device_TableHead_Rowid": "",
|
||||
"Device_TableHead_SSID": "",
|
||||
"Device_TableHead_SourcePlugin": "",
|
||||
"Device_TableHead_Status": "",
|
||||
"Device_TableHead_SyncHubNodeName": "",
|
||||
"Device_TableHead_Type": "",
|
||||
"Device_TableHead_Vendor": "",
|
||||
"Device_TableHead_Vlan": "",
|
||||
"Device_Table_Not_Network_Device": "",
|
||||
"Device_Table_info": "",
|
||||
"Device_Table_nav_next": "",
|
||||
"Device_Table_nav_prev": "",
|
||||
"Device_Tablelenght": "",
|
||||
"Device_Tablelenght_all": "",
|
||||
"Device_Title": "",
|
||||
"Devices_Filters": "",
|
||||
"ENABLE_PLUGINS_description": "",
|
||||
"ENABLE_PLUGINS_name": "",
|
||||
"ENCRYPTION_KEY_description": "",
|
||||
"ENCRYPTION_KEY_name": "",
|
||||
"Email_display_name": "",
|
||||
"Email_icon": "",
|
||||
"Events_Loading": "",
|
||||
"Events_Periodselect_All": "",
|
||||
"Events_Periodselect_LastMonth": "",
|
||||
"Events_Periodselect_LastWeek": "",
|
||||
"Events_Periodselect_LastYear": "",
|
||||
"Events_Periodselect_today": "",
|
||||
"Events_Searchbox": "",
|
||||
"Events_Shortcut_AllEvents": "",
|
||||
"Events_Shortcut_DownAlerts": "",
|
||||
"Events_Shortcut_Events": "",
|
||||
"Events_Shortcut_MissSessions": "",
|
||||
"Events_Shortcut_NewDevices": "",
|
||||
"Events_Shortcut_Sessions": "",
|
||||
"Events_Shortcut_VoidSessions": "",
|
||||
"Events_TableHead_AdditionalInfo": "",
|
||||
"Events_TableHead_Connection": "",
|
||||
"Events_TableHead_Date": "",
|
||||
"Events_TableHead_Device": "",
|
||||
"Events_TableHead_Disconnection": "",
|
||||
"Events_TableHead_Duration": "",
|
||||
"Events_TableHead_DurationOrder": "",
|
||||
"Events_TableHead_EventType": "",
|
||||
"Events_TableHead_IP": "",
|
||||
"Events_TableHead_IPOrder": "",
|
||||
"Events_TableHead_Order": "",
|
||||
"Events_TableHead_Owner": "",
|
||||
"Events_TableHead_PendingAlert": "",
|
||||
"Events_Table_info": "",
|
||||
"Events_Table_nav_next": "",
|
||||
"Events_Table_nav_prev": "",
|
||||
"Events_Tablelenght": "",
|
||||
"Events_Tablelenght_all": "",
|
||||
"Events_Title": "",
|
||||
"FakeMAC_hover": "",
|
||||
"FieldLock_Error": "",
|
||||
"FieldLock_Lock_Tooltip": "",
|
||||
"FieldLock_Locked": "",
|
||||
"FieldLock_SaveBeforeLocking": "",
|
||||
"FieldLock_Source_Label": "",
|
||||
"FieldLock_Unlock_Tooltip": "",
|
||||
"FieldLock_Unlocked": "",
|
||||
"GRAPHQL_PORT_description": "",
|
||||
"GRAPHQL_PORT_name": "",
|
||||
"Gen_Action": "",
|
||||
"Gen_Add": "",
|
||||
"Gen_AddDevice": "",
|
||||
"Gen_Add_All": "",
|
||||
"Gen_All_Devices": "",
|
||||
"Gen_Archived": "",
|
||||
"Gen_AreYouSure": "",
|
||||
"Gen_Backup": "",
|
||||
"Gen_Cancel": "",
|
||||
"Gen_Change": "",
|
||||
"Gen_Copy": "",
|
||||
"Gen_CopyToClipboard": "",
|
||||
"Gen_DataUpdatedUITakesTime": "",
|
||||
"Gen_Delete": "",
|
||||
"Gen_DeleteAll": "",
|
||||
"Gen_Description": "",
|
||||
"Gen_Down": "",
|
||||
"Gen_Error": "",
|
||||
"Gen_Filter": "",
|
||||
"Gen_Flapping": "",
|
||||
"Gen_Generate": "",
|
||||
"Gen_InvalidMac": "",
|
||||
"Gen_Invalid_Value": "",
|
||||
"Gen_LockedDB": "",
|
||||
"Gen_NetworkMask": "",
|
||||
"Gen_New": "",
|
||||
"Gen_Offline": "",
|
||||
"Gen_Okay": "",
|
||||
"Gen_Online": "",
|
||||
"Gen_Purge": "",
|
||||
"Gen_ReadDocs": "",
|
||||
"Gen_Remove_All": "",
|
||||
"Gen_Remove_Last": "",
|
||||
"Gen_Reset": "",
|
||||
"Gen_Restore": "",
|
||||
"Gen_Run": "",
|
||||
"Gen_Save": "",
|
||||
"Gen_Saved": "",
|
||||
"Gen_Search": "",
|
||||
"Gen_Select": "",
|
||||
"Gen_SelectIcon": "",
|
||||
"Gen_SelectToPreview": "",
|
||||
"Gen_Selected_Devices": "",
|
||||
"Gen_Sleeping": "",
|
||||
"Gen_Subnet": "",
|
||||
"Gen_Switch": "",
|
||||
"Gen_Upd": "",
|
||||
"Gen_Upd_Fail": "",
|
||||
"Gen_Update": "",
|
||||
"Gen_Update_Value": "",
|
||||
"Gen_ValidIcon": "",
|
||||
"Gen_Warning": "",
|
||||
"Gen_Work_In_Progress": "",
|
||||
"Gen_create_new_device": "",
|
||||
"Gen_create_new_device_info": "",
|
||||
"General_display_name": "",
|
||||
"General_icon": "",
|
||||
"HRS_TO_KEEP_NEWDEV_description": "",
|
||||
"HRS_TO_KEEP_NEWDEV_name": "",
|
||||
"HRS_TO_KEEP_OFFDEV_description": "",
|
||||
"HRS_TO_KEEP_OFFDEV_name": "",
|
||||
"LOADED_PLUGINS_description": "",
|
||||
"LOADED_PLUGINS_name": "",
|
||||
"LOG_LEVEL_description": "",
|
||||
"LOG_LEVEL_name": "",
|
||||
"Loading": "",
|
||||
"Login_Box": "",
|
||||
"Login_Default_PWD": "",
|
||||
"Login_Info": "",
|
||||
"Login_Psw-box": "",
|
||||
"Login_Psw_alert": "",
|
||||
"Login_Psw_folder": "",
|
||||
"Login_Psw_new": "",
|
||||
"Login_Psw_run": "",
|
||||
"Login_Remember": "",
|
||||
"Login_Remember_small": "",
|
||||
"Login_Submit": "",
|
||||
"Login_Toggle_Alert_headline": "",
|
||||
"Login_Toggle_Info": "",
|
||||
"Login_Toggle_Info_headline": "",
|
||||
"Maint_PurgeLog": "",
|
||||
"Maint_RestartServer": "",
|
||||
"Maint_Restart_Server_noti_text": "",
|
||||
"Maintenance_InitCheck": "",
|
||||
"Maintenance_InitCheck_Checking": "",
|
||||
"Maintenance_InitCheck_QuickSetupGuide": "",
|
||||
"Maintenance_InitCheck_Success": "",
|
||||
"Maintenance_ReCheck": "",
|
||||
"Maintenance_Running_Version": "",
|
||||
"Maintenance_Status": "",
|
||||
"Maintenance_Title": "",
|
||||
"Maintenance_Tool_DownloadConfig": "",
|
||||
"Maintenance_Tool_DownloadConfig_text": "",
|
||||
"Maintenance_Tool_DownloadWorkflows": "",
|
||||
"Maintenance_Tool_DownloadWorkflows_text": "",
|
||||
"Maintenance_Tool_ExportCSV": "",
|
||||
"Maintenance_Tool_ExportCSV_noti": "",
|
||||
"Maintenance_Tool_ExportCSV_noti_text": "",
|
||||
"Maintenance_Tool_ExportCSV_text": "",
|
||||
"Maintenance_Tool_ImportCSV": "",
|
||||
"Maintenance_Tool_ImportCSV_noti": "",
|
||||
"Maintenance_Tool_ImportCSV_noti_text": "",
|
||||
"Maintenance_Tool_ImportCSV_text": "",
|
||||
"Maintenance_Tool_ImportConfig_noti": "",
|
||||
"Maintenance_Tool_ImportPastedCSV": "",
|
||||
"Maintenance_Tool_ImportPastedCSV_noti_text": "",
|
||||
"Maintenance_Tool_ImportPastedCSV_text": "",
|
||||
"Maintenance_Tool_ImportPastedConfig": "",
|
||||
"Maintenance_Tool_ImportPastedConfig_noti_text": "",
|
||||
"Maintenance_Tool_ImportPastedConfig_text": "",
|
||||
"Maintenance_Tool_UnlockFields": "",
|
||||
"Maintenance_Tool_UnlockFields_noti": "",
|
||||
"Maintenance_Tool_UnlockFields_noti_text": "",
|
||||
"Maintenance_Tool_UnlockFields_text": "",
|
||||
"Maintenance_Tool_arpscansw": "",
|
||||
"Maintenance_Tool_arpscansw_noti": "",
|
||||
"Maintenance_Tool_arpscansw_noti_text": "",
|
||||
"Maintenance_Tool_arpscansw_text": "",
|
||||
"Maintenance_Tool_backup": "",
|
||||
"Maintenance_Tool_backup_noti": "",
|
||||
"Maintenance_Tool_backup_noti_text": "",
|
||||
"Maintenance_Tool_backup_text": "",
|
||||
"Maintenance_Tool_check_visible": "",
|
||||
"Maintenance_Tool_clearSourceFields_selected": "",
|
||||
"Maintenance_Tool_clearSourceFields_selected_noti": "",
|
||||
"Maintenance_Tool_clearSourceFields_selected_text": "",
|
||||
"Maintenance_Tool_darkmode": "",
|
||||
"Maintenance_Tool_darkmode_noti": "",
|
||||
"Maintenance_Tool_darkmode_noti_text": "",
|
||||
"Maintenance_Tool_darkmode_text": "",
|
||||
"Maintenance_Tool_del_ActHistory": "",
|
||||
"Maintenance_Tool_del_ActHistory_noti": "",
|
||||
"Maintenance_Tool_del_ActHistory_noti_text": "",
|
||||
"Maintenance_Tool_del_ActHistory_text": "",
|
||||
"Maintenance_Tool_del_alldev": "",
|
||||
"Maintenance_Tool_del_alldev_noti": "",
|
||||
"Maintenance_Tool_del_alldev_noti_text": "",
|
||||
"Maintenance_Tool_del_alldev_text": "",
|
||||
"Maintenance_Tool_del_allevents": "",
|
||||
"Maintenance_Tool_del_allevents30": "",
|
||||
"Maintenance_Tool_del_allevents30_noti": "",
|
||||
"Maintenance_Tool_del_allevents30_noti_text": "",
|
||||
"Maintenance_Tool_del_allevents30_text": "",
|
||||
"Maintenance_Tool_del_allevents_noti": "",
|
||||
"Maintenance_Tool_del_allevents_noti_text": "",
|
||||
"Maintenance_Tool_del_allevents_text": "",
|
||||
"Maintenance_Tool_del_empty_macs": "",
|
||||
"Maintenance_Tool_del_empty_macs_noti": "",
|
||||
"Maintenance_Tool_del_empty_macs_noti_text": "",
|
||||
"Maintenance_Tool_del_empty_macs_text": "",
|
||||
"Maintenance_Tool_del_selecteddev": "",
|
||||
"Maintenance_Tool_del_selecteddev_text": "",
|
||||
"Maintenance_Tool_del_unknowndev": "",
|
||||
"Maintenance_Tool_del_unknowndev_noti": "",
|
||||
"Maintenance_Tool_del_unknowndev_noti_text": "",
|
||||
"Maintenance_Tool_del_unknowndev_text": "",
|
||||
"Maintenance_Tool_del_unlockFields_selecteddev_text": "",
|
||||
"Maintenance_Tool_displayed_columns_text": "",
|
||||
"Maintenance_Tool_drag_me": "",
|
||||
"Maintenance_Tool_order_columns_text": "",
|
||||
"Maintenance_Tool_purgebackup": "",
|
||||
"Maintenance_Tool_purgebackup_noti": "",
|
||||
"Maintenance_Tool_purgebackup_noti_text": "",
|
||||
"Maintenance_Tool_purgebackup_text": "",
|
||||
"Maintenance_Tool_restore": "",
|
||||
"Maintenance_Tool_restore_noti": "",
|
||||
"Maintenance_Tool_restore_noti_text": "",
|
||||
"Maintenance_Tool_restore_text": "",
|
||||
"Maintenance_Tool_unlockFields_selecteddev": "",
|
||||
"Maintenance_Tool_unlockFields_selecteddev_noti": "",
|
||||
"Maintenance_Tool_upgrade_database_noti": "",
|
||||
"Maintenance_Tool_upgrade_database_noti_text": "",
|
||||
"Maintenance_Tool_upgrade_database_text": "",
|
||||
"Maintenance_Tools_Tab_BackupRestore": "",
|
||||
"Maintenance_Tools_Tab_Logging": "",
|
||||
"Maintenance_Tools_Tab_Settings": "",
|
||||
"Maintenance_Tools_Tab_Tools": "",
|
||||
"Maintenance_Tools_Tab_UISettings": "",
|
||||
"Maintenance_arp_status": "",
|
||||
"Maintenance_arp_status_off": "",
|
||||
"Maintenance_arp_status_on": "",
|
||||
"Maintenance_built_on": "",
|
||||
"Maintenance_current_version": "",
|
||||
"Maintenance_database_backup": "",
|
||||
"Maintenance_database_backup_found": "",
|
||||
"Maintenance_database_backup_total": "",
|
||||
"Maintenance_database_lastmod": "",
|
||||
"Maintenance_database_path": "",
|
||||
"Maintenance_database_rows": "",
|
||||
"Maintenance_database_size": "",
|
||||
"Maintenance_lang_selector_apply": "",
|
||||
"Maintenance_lang_selector_empty": "",
|
||||
"Maintenance_lang_selector_lable": "",
|
||||
"Maintenance_lang_selector_text": "",
|
||||
"Maintenance_new_version": "",
|
||||
"Maintenance_themeselector_apply": "",
|
||||
"Maintenance_themeselector_empty": "",
|
||||
"Maintenance_themeselector_lable": "",
|
||||
"Maintenance_themeselector_text": "",
|
||||
"Maintenance_version": "",
|
||||
"NETWORK_DEVICE_TYPES_description": "",
|
||||
"NETWORK_DEVICE_TYPES_name": "",
|
||||
"Navigation_About": "",
|
||||
"Navigation_AppEvents": "",
|
||||
"Navigation_Devices": "",
|
||||
"Navigation_Donations": "",
|
||||
"Navigation_Events": "",
|
||||
"Navigation_Integrations": "",
|
||||
"Navigation_Maintenance": "",
|
||||
"Navigation_Monitoring": "",
|
||||
"Navigation_Network": "",
|
||||
"Navigation_Notifications": "",
|
||||
"Navigation_Plugins": "",
|
||||
"Navigation_Presence": "",
|
||||
"Navigation_Report": "",
|
||||
"Navigation_Settings": "",
|
||||
"Navigation_SystemInfo": "",
|
||||
"Navigation_Workflows": "",
|
||||
"Network_Assign": "",
|
||||
"Network_Cant_Assign": "",
|
||||
"Network_Cant_Assign_No_Node_Selected": "",
|
||||
"Network_Configuration_Error": "",
|
||||
"Network_Connected": "",
|
||||
"Network_Devices": "",
|
||||
"Network_ManageAdd": "",
|
||||
"Network_ManageAdd_Name": "",
|
||||
"Network_ManageAdd_Name_text": "",
|
||||
"Network_ManageAdd_Port": "",
|
||||
"Network_ManageAdd_Port_text": "",
|
||||
"Network_ManageAdd_Submit": "",
|
||||
"Network_ManageAdd_Type": "",
|
||||
"Network_ManageAdd_Type_text": "",
|
||||
"Network_ManageAssign": "",
|
||||
"Network_ManageDel": "",
|
||||
"Network_ManageDel_Name": "",
|
||||
"Network_ManageDel_Name_text": "",
|
||||
"Network_ManageDel_Submit": "",
|
||||
"Network_ManageDevices": "",
|
||||
"Network_ManageEdit": "",
|
||||
"Network_ManageEdit_ID": "",
|
||||
"Network_ManageEdit_ID_text": "",
|
||||
"Network_ManageEdit_Name": "",
|
||||
"Network_ManageEdit_Name_text": "",
|
||||
"Network_ManageEdit_Port": "",
|
||||
"Network_ManageEdit_Port_text": "",
|
||||
"Network_ManageEdit_Submit": "",
|
||||
"Network_ManageEdit_Type": "",
|
||||
"Network_ManageEdit_Type_text": "",
|
||||
"Network_ManageLeaf": "",
|
||||
"Network_ManageUnassign": "",
|
||||
"Network_NoAssignedDevices": "",
|
||||
"Network_NoDevices": "",
|
||||
"Network_Node": "",
|
||||
"Network_Node_Name": "",
|
||||
"Network_Parent": "",
|
||||
"Network_Root": "",
|
||||
"Network_Root_Not_Configured": "",
|
||||
"Network_Root_Unconfigurable": "",
|
||||
"Network_ShowArchived": "",
|
||||
"Network_ShowOffline": "",
|
||||
"Network_Table_Hostname": "",
|
||||
"Network_Table_IP": "",
|
||||
"Network_Table_State": "",
|
||||
"Network_Title": "",
|
||||
"Network_UnassignedDevices": "",
|
||||
"Notifications_All": "",
|
||||
"Notifications_Mark_All_Read": "",
|
||||
"PIALERT_WEB_PASSWORD_description": "",
|
||||
"PIALERT_WEB_PASSWORD_name": "",
|
||||
"PIALERT_WEB_PROTECTION_description": "",
|
||||
"PIALERT_WEB_PROTECTION_name": "",
|
||||
"PLUGINS_KEEP_HIST_description": "",
|
||||
"PLUGINS_KEEP_HIST_name": "",
|
||||
"PRAGMA_JOURNAL_SIZE_LIMIT_description": "",
|
||||
"PRAGMA_JOURNAL_SIZE_LIMIT_name": "",
|
||||
"Plugins_DeleteAll": "",
|
||||
"Plugins_Filters_Mac": "",
|
||||
"Plugins_History": "",
|
||||
"Plugins_Obj_DeleteListed": "",
|
||||
"Plugins_Objects": "",
|
||||
"Plugins_Out_of": "",
|
||||
"Plugins_Unprocessed_Events": "",
|
||||
"Plugins_no_control": "",
|
||||
"Presence_CalHead_day": "",
|
||||
"Presence_CalHead_lang": "",
|
||||
"Presence_CalHead_month": "",
|
||||
"Presence_CalHead_quarter": "",
|
||||
"Presence_CalHead_week": "",
|
||||
"Presence_CalHead_year": "",
|
||||
"Presence_CallHead_Devices": "",
|
||||
"Presence_Key_OnlineNow": "",
|
||||
"Presence_Key_OnlineNow_desc": "",
|
||||
"Presence_Key_OnlinePast": "",
|
||||
"Presence_Key_OnlinePastMiss": "",
|
||||
"Presence_Key_OnlinePastMiss_desc": "",
|
||||
"Presence_Key_OnlinePast_desc": "",
|
||||
"Presence_Loading": "",
|
||||
"Presence_Shortcut_AllDevices": "",
|
||||
"Presence_Shortcut_Archived": "",
|
||||
"Presence_Shortcut_Connected": "",
|
||||
"Presence_Shortcut_Devices": "",
|
||||
"Presence_Shortcut_DownAlerts": "",
|
||||
"Presence_Shortcut_Favorites": "",
|
||||
"Presence_Shortcut_NewDevices": "",
|
||||
"Presence_Title": "",
|
||||
"REFRESH_FQDN_description": "",
|
||||
"REFRESH_FQDN_name": "",
|
||||
"REPORT_DASHBOARD_URL_description": "",
|
||||
"REPORT_DASHBOARD_URL_name": "",
|
||||
"REPORT_ERROR": "",
|
||||
"REPORT_MAIL_description": "",
|
||||
"REPORT_MAIL_name": "",
|
||||
"REPORT_TITLE": "",
|
||||
"RandomMAC_hover": "",
|
||||
"Reports_Sent_Log": "",
|
||||
"SCAN_SUBNETS_description": "",
|
||||
"SCAN_SUBNETS_name": "",
|
||||
"SYSTEM_TITLE": "",
|
||||
"Setting_Override": "",
|
||||
"Setting_Override_Description": "",
|
||||
"Settings_Metadata_Toggle": "",
|
||||
"Settings_Show_Description": "",
|
||||
"Settings_device_Scanners_desync": "",
|
||||
"Settings_device_Scanners_desync_popup": "",
|
||||
"Speedtest_Results": "",
|
||||
"Systeminfo_AvailableIps": "",
|
||||
"Systeminfo_CPU": "",
|
||||
"Systeminfo_CPU_Cores": "",
|
||||
"Systeminfo_CPU_Name": "",
|
||||
"Systeminfo_CPU_Speed": "",
|
||||
"Systeminfo_CPU_Temp": "",
|
||||
"Systeminfo_CPU_Vendor": "",
|
||||
"Systeminfo_Client_Resolution": "",
|
||||
"Systeminfo_Client_User_Agent": "",
|
||||
"Systeminfo_General": "",
|
||||
"Systeminfo_General_Date": "",
|
||||
"Systeminfo_General_Date2": "",
|
||||
"Systeminfo_General_Full_Date": "",
|
||||
"Systeminfo_General_TimeZone": "",
|
||||
"Systeminfo_Memory": "",
|
||||
"Systeminfo_Memory_Total_Memory": "",
|
||||
"Systeminfo_Memory_Usage": "",
|
||||
"Systeminfo_Memory_Usage_Percent": "",
|
||||
"Systeminfo_Motherboard": "",
|
||||
"Systeminfo_Motherboard_BIOS": "",
|
||||
"Systeminfo_Motherboard_BIOS_Date": "",
|
||||
"Systeminfo_Motherboard_BIOS_Vendor": "",
|
||||
"Systeminfo_Motherboard_Manufactured": "",
|
||||
"Systeminfo_Motherboard_Name": "",
|
||||
"Systeminfo_Motherboard_Revision": "",
|
||||
"Systeminfo_Network": "",
|
||||
"Systeminfo_Network_Accept_Encoding": "",
|
||||
"Systeminfo_Network_Accept_Language": "",
|
||||
"Systeminfo_Network_Connection_Port": "",
|
||||
"Systeminfo_Network_HTTP_Host": "",
|
||||
"Systeminfo_Network_HTTP_Referer": "",
|
||||
"Systeminfo_Network_HTTP_Referer_String": "",
|
||||
"Systeminfo_Network_Hardware": "",
|
||||
"Systeminfo_Network_Hardware_Interface_Mask": "",
|
||||
"Systeminfo_Network_Hardware_Interface_Name": "",
|
||||
"Systeminfo_Network_Hardware_Interface_RX": "",
|
||||
"Systeminfo_Network_Hardware_Interface_TX": "",
|
||||
"Systeminfo_Network_IP": "",
|
||||
"Systeminfo_Network_IP_Connection": "",
|
||||
"Systeminfo_Network_IP_Server": "",
|
||||
"Systeminfo_Network_MIME": "",
|
||||
"Systeminfo_Network_Request_Method": "",
|
||||
"Systeminfo_Network_Request_Time": "",
|
||||
"Systeminfo_Network_Request_URI": "",
|
||||
"Systeminfo_Network_Secure_Connection": "",
|
||||
"Systeminfo_Network_Secure_Connection_String": "",
|
||||
"Systeminfo_Network_Server_Name": "",
|
||||
"Systeminfo_Network_Server_Name_String": "",
|
||||
"Systeminfo_Network_Server_Query": "",
|
||||
"Systeminfo_Network_Server_Query_String": "",
|
||||
"Systeminfo_Network_Server_Version": "",
|
||||
"Systeminfo_Services": "",
|
||||
"Systeminfo_Services_Description": "",
|
||||
"Systeminfo_Services_Name": "",
|
||||
"Systeminfo_Storage": "",
|
||||
"Systeminfo_Storage_Device": "",
|
||||
"Systeminfo_Storage_Mount": "",
|
||||
"Systeminfo_Storage_Size": "",
|
||||
"Systeminfo_Storage_Type": "",
|
||||
"Systeminfo_Storage_Usage": "",
|
||||
"Systeminfo_Storage_Usage_Free": "",
|
||||
"Systeminfo_Storage_Usage_Mount": "",
|
||||
"Systeminfo_Storage_Usage_Total": "",
|
||||
"Systeminfo_Storage_Usage_Used": "",
|
||||
"Systeminfo_System": "",
|
||||
"Systeminfo_System_AVG": "",
|
||||
"Systeminfo_System_Architecture": "",
|
||||
"Systeminfo_System_Kernel": "",
|
||||
"Systeminfo_System_OSVersion": "",
|
||||
"Systeminfo_System_Running_Processes": "",
|
||||
"Systeminfo_System_System": "",
|
||||
"Systeminfo_System_Uname": "",
|
||||
"Systeminfo_System_Uptime": "",
|
||||
"Systeminfo_This_Client": "",
|
||||
"Systeminfo_USB_Devices": "",
|
||||
"TICKER_MIGRATE_TO_NETALERTX": "",
|
||||
"TIMEZONE_description": "",
|
||||
"TIMEZONE_name": "",
|
||||
"UI_DEV_SECTIONS_description": "",
|
||||
"UI_DEV_SECTIONS_name": "",
|
||||
"UI_ICONS_description": "",
|
||||
"UI_ICONS_name": "",
|
||||
"UI_LANG_description": "",
|
||||
"UI_LANG_name": "",
|
||||
"UI_MY_DEVICES_description": "",
|
||||
"UI_MY_DEVICES_name": "",
|
||||
"UI_NOT_RANDOM_MAC_description": "",
|
||||
"UI_NOT_RANDOM_MAC_name": "",
|
||||
"UI_PRESENCE_description": "",
|
||||
"UI_PRESENCE_name": "",
|
||||
"UI_REFRESH_description": "",
|
||||
"UI_REFRESH_name": "",
|
||||
"VERSION_description": "",
|
||||
"VERSION_name": "",
|
||||
"WF_Action_Add": "",
|
||||
"WF_Action_field": "",
|
||||
"WF_Action_type": "",
|
||||
"WF_Action_value": "",
|
||||
"WF_Actions": "",
|
||||
"WF_Add": "",
|
||||
"WF_Add_Condition": "",
|
||||
"WF_Add_Group": "",
|
||||
"WF_Condition_field": "",
|
||||
"WF_Condition_operator": "",
|
||||
"WF_Condition_value": "",
|
||||
"WF_Conditions": "",
|
||||
"WF_Conditions_logic_rules": "",
|
||||
"WF_Duplicate": "",
|
||||
"WF_Enabled": "",
|
||||
"WF_Export": "",
|
||||
"WF_Export_Copy": "",
|
||||
"WF_Import": "",
|
||||
"WF_Import_Copy": "",
|
||||
"WF_Name": "",
|
||||
"WF_Remove": "",
|
||||
"WF_Remove_Copy": "",
|
||||
"WF_Save": "",
|
||||
"WF_Trigger": "",
|
||||
"WF_Trigger_event_type": "",
|
||||
"WF_Trigger_type": "",
|
||||
"add_icon_event_tooltip": "",
|
||||
"add_option_event_tooltip": "",
|
||||
"copy_icons_event_tooltip": "",
|
||||
"devices_old": "",
|
||||
"general_event_description": "",
|
||||
"general_event_title": "",
|
||||
"go_to_device_event_tooltip": "",
|
||||
"go_to_node_event_tooltip": "",
|
||||
"new_version_available": "",
|
||||
"report_guid": "",
|
||||
"report_guid_missing": "",
|
||||
"report_select_format": "",
|
||||
"report_time": "",
|
||||
"run_event_tooltip": "",
|
||||
"select_icon_event_tooltip": "",
|
||||
"settings_core_icon": "",
|
||||
"settings_core_label": "",
|
||||
"settings_device_scanners": "",
|
||||
"settings_device_scanners_icon": "",
|
||||
"settings_device_scanners_info": "",
|
||||
"settings_device_scanners_label": "",
|
||||
"settings_enabled": "",
|
||||
"settings_enabled_icon": "",
|
||||
"settings_expand_all": "",
|
||||
"settings_imported": "",
|
||||
"settings_imported_label": "",
|
||||
"settings_missing": "",
|
||||
"settings_missing_block": "",
|
||||
"settings_old": "",
|
||||
"settings_other_scanners": "",
|
||||
"settings_other_scanners_icon": "",
|
||||
"settings_other_scanners_label": "",
|
||||
"settings_publishers": "",
|
||||
"settings_publishers_icon": "",
|
||||
"settings_publishers_info": "",
|
||||
"settings_publishers_label": "",
|
||||
"settings_readonly": "",
|
||||
"settings_saved": "",
|
||||
"settings_system_icon": "",
|
||||
"settings_system_label": "",
|
||||
"settings_update_item_warning": "",
|
||||
"test_event_tooltip": ""
|
||||
}
|
||||
@@ -203,8 +203,8 @@
|
||||
"Device_MultiEdit_MassActions": "Azioni di massa:",
|
||||
"Device_MultiEdit_No_Devices": "Nessun dispositivo selezionato.",
|
||||
"Device_MultiEdit_Tooltip": "Attento. Facendo clic verrà applicato il valore sulla sinistra a tutti i dispositivi selezionati sopra.",
|
||||
"Device_NextScan_Imminent": "imminente",
|
||||
"Device_NextScan_In": "Prossima scansione in ",
|
||||
"Device_NextScan_Imminent": "Imminente...",
|
||||
"Device_NextScan_In": "Prossima scansione tra circa ",
|
||||
"Device_NoData_Help": "Se i dispositivi non vengono visualizzati dopo la scansione, controlla l'impostazione SCAN_SUBNETS e la <a href=\"https://docs.netalertx.com/SUBNETS\" target=\"_blank\">documentazione</a>.",
|
||||
"Device_NoData_Scanning": "In attesa della prima scansione: potrebbero volerci diversi minuti dopo la configurazione iniziale.",
|
||||
"Device_NoData_Title": "Ancora nessun dispositivo trovato",
|
||||
@@ -804,4 +804,4 @@
|
||||
"settings_system_label": "Sistema",
|
||||
"settings_update_item_warning": "Aggiorna il valore qui sotto. Fai attenzione a seguire il formato precedente. <b>La convalida non viene eseguita.</b>",
|
||||
"test_event_tooltip": "Salva le modifiche prima di provare le nuove impostazioni."
|
||||
}
|
||||
}
|
||||
@@ -804,4 +804,4 @@
|
||||
"settings_system_label": "システム",
|
||||
"settings_update_item_warning": "以下の値を更新してください。以前のフォーマットに従うよう注意してください。<b>検証は行われません。</b>",
|
||||
"test_event_tooltip": "設定をテストする前に、まず変更を保存してください。"
|
||||
}
|
||||
}
|
||||
@@ -2,12 +2,13 @@
|
||||
|
||||
// ###################################
|
||||
// ## Languages
|
||||
// ## Look-up here: http://www.lingoes.net/en/translator/langcode.htm
|
||||
// ###################################
|
||||
|
||||
$defaultLang = "en_us";
|
||||
|
||||
// Load the canonical language list from languages.json — do not hardcode here.
|
||||
$_langJsonPath = dirname(__FILE__) . '/languages.json';
|
||||
$_langJsonPath = dirname(__FILE__) . '/language_definitions/languages.json';
|
||||
$_langJson = json_decode(file_get_contents($_langJsonPath), true);
|
||||
$allLanguages = array_column($_langJson['languages'], 'code');
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
{ "code": "en_us", "display": "English (en_us)" },
|
||||
{ "code": "es_es", "display": "Spanish (es_es)" },
|
||||
{ "code": "fa_fa", "display": "Farsi (fa_fa)" },
|
||||
{ "code": "id_id", "display": "Indonesian (id_id)" },
|
||||
{ "code": "fr_fr", "display": "French (fr_fr)" },
|
||||
{ "code": "it_it", "display": "Italian (it_it)" },
|
||||
{ "code": "ja_jp", "display": "Japanese (ja_jp)" },
|
||||
@@ -46,7 +46,7 @@ def load_language_codes(languages_json_path):
|
||||
if __name__ == "__main__":
|
||||
current_path = os.path.dirname(os.path.abspath(__file__))
|
||||
# language codes are loaded from languages.json — add a new language there
|
||||
languages_json = os.path.join(current_path, "languages.json")
|
||||
languages_json = os.path.join(current_path, "language_definitions/languages.json")
|
||||
codes = load_language_codes(languages_json)
|
||||
file_paths = [os.path.join(current_path, f"{code}.json") for code in codes]
|
||||
merge_translations(file_paths[0], file_paths[1:])
|
||||
|
||||
@@ -139,7 +139,7 @@
|
||||
"DevDetail_SessionTable_Duration": "Продолжительность",
|
||||
"DevDetail_SessionTable_IP": "IP",
|
||||
"DevDetail_SessionTable_Order": "Порядок",
|
||||
"DevDetail_Shortcut_CurrentStatus": "Текущий статус",
|
||||
"DevDetail_Shortcut_CurrentStatus": "Статус",
|
||||
"DevDetail_Shortcut_DownAlerts": "Оповещения о сбое",
|
||||
"DevDetail_Shortcut_Presence": "Присутствие",
|
||||
"DevDetail_Shortcut_Sessions": "Сеансы",
|
||||
@@ -203,16 +203,16 @@
|
||||
"Device_MultiEdit_MassActions": "Массовые действия:",
|
||||
"Device_MultiEdit_No_Devices": "Устройства не выбраны.",
|
||||
"Device_MultiEdit_Tooltip": "Осторожно. При нажатии на эту кнопку значение слева будет применено ко всем устройствам, выбранным выше.",
|
||||
"Device_NextScan_Imminent": "",
|
||||
"Device_NextScan_In": "",
|
||||
"Device_NoData_Help": "",
|
||||
"Device_NoData_Scanning": "",
|
||||
"Device_NoData_Title": "",
|
||||
"Device_NextScan_Imminent": "Предстоящий...",
|
||||
"Device_NextScan_In": "Следующее сканирование примерно через· ",
|
||||
"Device_NoData_Help": "Если устройства не отображаются после сканирования, проверьте настройку SCAN_SUBNETS и <a href=\"https://docs.netalertx.com/SUBNETS\" target=\"_blank\">документацию</a>.",
|
||||
"Device_NoData_Scanning": "Ожидание первого сканирования — это может занять несколько минут после первоначальной настройки.",
|
||||
"Device_NoData_Title": "Устройства пока не найдены",
|
||||
"Device_Save_Failed": "Не удалось сохранить устройство",
|
||||
"Device_Save_Unauthorized": "Не авторизован - недействительный токен API",
|
||||
"Device_Saved_Success": "Устройство успешно сохранено",
|
||||
"Device_Saved_Unexpected": "Обновление устройства дало неожиданный ответ",
|
||||
"Device_Scanning": "",
|
||||
"Device_Scanning": "Сканирование...",
|
||||
"Device_Searchbox": "Поиск",
|
||||
"Device_Shortcut_AllDevices": "Мои устройства",
|
||||
"Device_Shortcut_AllNodes": "Все узлы",
|
||||
@@ -224,7 +224,7 @@
|
||||
"Device_Shortcut_Favorites": "Избранные",
|
||||
"Device_Shortcut_NewDevices": "Новые устройства",
|
||||
"Device_Shortcut_OnlineChart": "Присутствие устройств",
|
||||
"Device_Shortcut_Unstable": "",
|
||||
"Device_Shortcut_Unstable": "Нестабильный",
|
||||
"Device_TableHead_AlertDown": "Оповещение о сост. ВЫКЛ",
|
||||
"Device_TableHead_Connected_Devices": "Соединения",
|
||||
"Device_TableHead_CustomProps": "Свойства / Действия",
|
||||
@@ -322,7 +322,7 @@
|
||||
"Gen_AddDevice": "Добавить устройство",
|
||||
"Gen_Add_All": "Добавить все",
|
||||
"Gen_All_Devices": "Все устройства",
|
||||
"Gen_Archived": "",
|
||||
"Gen_Archived": "Архивировано",
|
||||
"Gen_AreYouSure": "Вы уверены?",
|
||||
"Gen_Backup": "Запустить резервное копирование",
|
||||
"Gen_Cancel": "Отмена",
|
||||
@@ -333,7 +333,7 @@
|
||||
"Gen_Delete": "Удалить",
|
||||
"Gen_DeleteAll": "Удалить все",
|
||||
"Gen_Description": "Описание",
|
||||
"Gen_Down": "",
|
||||
"Gen_Down": "Лежит",
|
||||
"Gen_Error": "Ошибка",
|
||||
"Gen_Filter": "Фильтр",
|
||||
"Gen_Flapping": "",
|
||||
@@ -342,7 +342,7 @@
|
||||
"Gen_Invalid_Value": "Введено некорректное значение",
|
||||
"Gen_LockedDB": "ОШИБКА - Возможно, база данных заблокирована. Проверьте инструменты разработчика F12 -> Консоль или повторите попытку позже.",
|
||||
"Gen_NetworkMask": "Маска сети",
|
||||
"Gen_New": "",
|
||||
"Gen_New": "Новый",
|
||||
"Gen_Offline": "Оффлайн",
|
||||
"Gen_Okay": "OK",
|
||||
"Gen_Online": "Онлайн",
|
||||
@@ -360,7 +360,7 @@
|
||||
"Gen_SelectIcon": "<i class=\"fa-solid fa-chevron-down fa-fade\"></i>",
|
||||
"Gen_SelectToPreview": "Выберите для предварительного просмотра",
|
||||
"Gen_Selected_Devices": "Выбранные устройства:",
|
||||
"Gen_Sleeping": "",
|
||||
"Gen_Sleeping": "Спящий",
|
||||
"Gen_Subnet": "Подсеть",
|
||||
"Gen_Switch": "Переключить",
|
||||
"Gen_Upd": "Успешное обновление",
|
||||
@@ -590,8 +590,8 @@
|
||||
"PIALERT_WEB_PROTECTION_name": "Включить вход",
|
||||
"PLUGINS_KEEP_HIST_description": "Сколько записей результатов сканирования истории плагинов следует хранить (для каждого плагина, а не для конкретного устройства).",
|
||||
"PLUGINS_KEEP_HIST_name": "История плагинов",
|
||||
"PRAGMA_JOURNAL_SIZE_LIMIT_description": "",
|
||||
"PRAGMA_JOURNAL_SIZE_LIMIT_name": "",
|
||||
"PRAGMA_JOURNAL_SIZE_LIMIT_description": "Максимальный размер SQLite WAL (журнал упреждающей записи) в МБ перед запуском автоматических контрольных точек. Более низкие значения (10–20 МБ) уменьшают использование диска/хранилища, но увеличивают загрузку ЦП во время сканирования. Более высокие значения (50–100 МБ) уменьшают нагрузку на процессор во время операций, но могут использовать больше оперативной памяти и дискового пространства. Значение по умолчанию <code>50 МБ</code> компенсирует и то, и другое. Полезно для систем с ограниченными ресурсами, таких как устройства NAS с SD-картами. Перезапустите сервер, чтобы изменения вступили в силу после сохранения настроек.",
|
||||
"PRAGMA_JOURNAL_SIZE_LIMIT_name": "Ограничение размера WAL (МБ)",
|
||||
"Plugins_DeleteAll": "Удалить все (фильтры игнорируются)",
|
||||
"Plugins_Filters_Mac": "Фильтр MAC-адреса",
|
||||
"Plugins_History": "История событий",
|
||||
@@ -804,4 +804,4 @@
|
||||
"settings_system_label": "Система",
|
||||
"settings_update_item_warning": "Обновить значение ниже. Будьте осторожны, следуя предыдущему формату. <b>Проверка не выполняется.</b>",
|
||||
"test_event_tooltip": "Сначала сохраните изменения, прежде чем проверять настройки."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,9 @@ services:
|
||||
- CHOWN
|
||||
- SETUID
|
||||
- SETGID
|
||||
sysctls:
|
||||
net.ipv4.conf.all.arp_ignore: 1
|
||||
net.ipv4.conf.all.arp_announce: 2
|
||||
volumes:
|
||||
- type: volume
|
||||
source: netalertx_data
|
||||
|
||||
@@ -13,6 +13,9 @@ services:
|
||||
- CHOWN
|
||||
- SETUID
|
||||
- SETGID
|
||||
sysctls:
|
||||
net.ipv4.conf.all.arp_ignore: 1
|
||||
net.ipv4.conf.all.arp_announce: 2
|
||||
volumes:
|
||||
- type: volume
|
||||
source: netalertx_data
|
||||
|
||||
33
install/production-filesystem/entrypoint.d/36-override-individual-settings.sh
Normal file → Executable file
33
install/production-filesystem/entrypoint.d/36-override-individual-settings.sh
Normal file → Executable file
@@ -9,28 +9,17 @@ if [ ! -f "${NETALERTX_CONFIG}/app.conf" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Helper: set or append config key safely
|
||||
set_config_value() {
|
||||
_key="$1"
|
||||
_value="$2"
|
||||
|
||||
# Remove newlines just in case
|
||||
_value=$(printf '%s' "$_value" | tr -d '\n\r')
|
||||
|
||||
# Escape sed-sensitive chars
|
||||
_escaped=$(printf '%s\n' "$_value" | sed 's/[\/&]/\\&/g')
|
||||
|
||||
if grep -q "^${_key}=" "${NETALERTX_CONFIG}/app.conf"; then
|
||||
sed -i "s|^${_key}=.*|${_key}=${_escaped}|" "${NETALERTX_CONFIG}/app.conf"
|
||||
else
|
||||
echo "${_key}=${_value}" >> "${NETALERTX_CONFIG}/app.conf"
|
||||
fi
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# LOADED_PLUGINS override
|
||||
# ------------------------------------------------------------
|
||||
if [ -n "${LOADED_PLUGINS:-}" ]; then
|
||||
echo "[ENV] Applying LOADED_PLUGINS override"
|
||||
set_config_value "LOADED_PLUGINS" "$LOADED_PLUGINS"
|
||||
value=$(printf '%s' "$LOADED_PLUGINS" | tr -d '\n\r')
|
||||
# declare delimiter for sed and escape it along with / and &
|
||||
delim='|'
|
||||
escaped=$(printf '%s\n' "$value" | sed "s/[\/${delim}&]/\\&/g")
|
||||
|
||||
if grep -q '^LOADED_PLUGINS=' "${NETALERTX_CONFIG}/app.conf"; then
|
||||
# use same delimiter when substituting
|
||||
sed -i "s${delim}^LOADED_PLUGINS=.*${delim}LOADED_PLUGINS=${escaped}${delim}" "${NETALERTX_CONFIG}/app.conf"
|
||||
else
|
||||
echo "LOADED_PLUGINS=${value}" >> "${NETALERTX_CONFIG}/app.conf"
|
||||
fi
|
||||
fi
|
||||
|
||||
84
install/production-filesystem/entrypoint.d/37-host-optimization.sh
Normal file → Executable file
84
install/production-filesystem/entrypoint.d/37-host-optimization.sh
Normal file → Executable file
@@ -1,92 +1,30 @@
|
||||
#!/bin/sh
|
||||
|
||||
# 37-host-optimization.sh: Apply and validate network optimizations (ARP flux fix)
|
||||
# 37-host-optimization.sh: Detect ARP flux sysctl configuration.
|
||||
#
|
||||
# This script improves detection accuracy by ensuring proper ARP behavior.
|
||||
# It attempts to apply sysctl settings and warns if not possible.
|
||||
# This script does not change host/kernel settings.
|
||||
|
||||
# --- Color Codes ---
|
||||
RED=$(printf '\033[1;31m')
|
||||
YELLOW=$(printf '\033[1;33m')
|
||||
RESET=$(printf '\033[0m')
|
||||
|
||||
# --- Skip flag ---
|
||||
if [ -n "${SKIP_OPTIMIZATIONS:-}" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# --- Helpers ---
|
||||
|
||||
get_sysctl() {
|
||||
sysctl -n "$1" 2>/dev/null || echo "unknown"
|
||||
}
|
||||
|
||||
set_sysctl_if_needed() {
|
||||
key="$1"
|
||||
expected="$2"
|
||||
|
||||
current="$(get_sysctl "$key")"
|
||||
|
||||
# Already correct
|
||||
if [ "$current" = "$expected" ]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Try to apply
|
||||
if sysctl -w "$key=$expected" >/dev/null 2>&1; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Failed
|
||||
return 1
|
||||
}
|
||||
|
||||
# --- Apply Settings (best effort) ---
|
||||
|
||||
failed=0
|
||||
|
||||
set_sysctl_if_needed net.ipv4.conf.all.arp_ignore 1 || failed=1
|
||||
set_sysctl_if_needed net.ipv4.conf.all.arp_announce 2 || failed=1
|
||||
set_sysctl_if_needed net.ipv4.conf.default.arp_ignore 1 || failed=1
|
||||
set_sysctl_if_needed net.ipv4.conf.default.arp_announce 2 || failed=1
|
||||
[ "$(sysctl -n net.ipv4.conf.all.arp_ignore 2>/dev/null || echo unknown)" = "1" ] || failed=1
|
||||
[ "$(sysctl -n net.ipv4.conf.all.arp_announce 2>/dev/null || echo unknown)" = "2" ] || failed=1
|
||||
|
||||
# --- Validate final state ---
|
||||
|
||||
all_ignore="$(get_sysctl net.ipv4.conf.all.arp_ignore)"
|
||||
all_announce="$(get_sysctl net.ipv4.conf.all.arp_announce)"
|
||||
|
||||
# --- Warning Output ---
|
||||
|
||||
if [ "$all_ignore" != "1" ] || [ "$all_announce" != "2" ]; then
|
||||
if [ "$failed" -eq 1 ]; then
|
||||
>&2 printf "%s" "${YELLOW}"
|
||||
>&2 cat <<EOF
|
||||
>&2 cat <<'EOF'
|
||||
══════════════════════════════════════════════════════════════════════════════
|
||||
⚠️ ATTENTION: ARP flux protection not enabled.
|
||||
|
||||
NetAlertX relies on ARP for device detection. Your system currently allows
|
||||
ARP replies from incorrect interfaces (ARP flux), which may result in:
|
||||
|
||||
• False devices being detected
|
||||
• IP/MAC mismatches
|
||||
• Flapping device states
|
||||
• Incorrect network topology
|
||||
|
||||
This is common when running in Docker or multi-interface environments.
|
||||
|
||||
──────────────────────────────────────────────────────────────────────────
|
||||
Recommended fix (Docker Compose):
|
||||
|
||||
sysctls:
|
||||
net.ipv4.conf.all.arp_ignore: 1
|
||||
net.ipv4.conf.all.arp_announce: 2
|
||||
|
||||
──────────────────────────────────────────────────────────────────────────
|
||||
Alternatively, apply on the host:
|
||||
⚠️ WARNING: ARP flux sysctls are not set.
|
||||
|
||||
Expected values:
|
||||
net.ipv4.conf.all.arp_ignore=1
|
||||
net.ipv4.conf.all.arp_announce=2
|
||||
|
||||
Detection accuracy may be reduced until this is configured.
|
||||
Detection accuracy may be reduced until configured.
|
||||
|
||||
See: https://docs.netalertx.com/docker-troubleshooting/arp-flux-sysctls/
|
||||
══════════════════════════════════════════════════════════════════════════════
|
||||
EOF
|
||||
>&2 printf "%s" "${RESET}"
|
||||
|
||||
@@ -86,10 +86,11 @@ for script in "${ENTRYPOINT_CHECKS}"/*; do
|
||||
fi
|
||||
script_name=$(basename "$script" | sed 's/^[0-9]*-//;s/\.(sh|py)$//;s/-/ /g')
|
||||
echo "--> ${script_name} "
|
||||
if [ -n "${SKIP_STARTUP_CHECKS:-}" ] && echo "${SKIP_STARTUP_CHECKS}" | grep -q "\b${script_name}\b"; then
|
||||
printf "%sskip%s\n" "${GREY}" "${RESET}"
|
||||
continue
|
||||
fi
|
||||
if [ -n "${SKIP_STARTUP_CHECKS:-}" ] &&
|
||||
printf '%s' "${SKIP_STARTUP_CHECKS}" | grep -wFq -- "${script_name}"; then
|
||||
printf "%sskip%s\n" "${GREY}" "${RESET}"
|
||||
continue
|
||||
fi
|
||||
|
||||
"$script"
|
||||
NETALERTX_DOCKER_ERROR_CHECK=$?
|
||||
|
||||
@@ -48,11 +48,13 @@ else
|
||||
log_error "python /app/server is not running"
|
||||
fi
|
||||
|
||||
# 5. Check port 20211 is open and contains "netalertx"
|
||||
if curl -sf --max-time 10 "http://localhost:${PORT:-20211}" | grep -i "netalertx" > /dev/null; then
|
||||
log_success "Port ${PORT:-20211} is responding and contains 'netalertx'"
|
||||
# 5. Check port 20211 is open
|
||||
CHECK_ADDR="${LISTEN_ADDR:-127.0.0.1}"
|
||||
[ "${CHECK_ADDR}" == "0.0.0.0" ] && CHECK_ADDR="127.0.0.1"
|
||||
if timeout 10 bash -c "</dev/tcp/${CHECK_ADDR}/${PORT:-20211}" 2>/dev/null; then
|
||||
log_success "Port ${PORT:-20211} is responding"
|
||||
else
|
||||
log_error "Port ${PORT:-20211} is not responding or doesn't contain 'netalertx'"
|
||||
log_error "Port ${PORT:-20211} is not responding"
|
||||
fi
|
||||
|
||||
# NOTE: GRAPHQL_PORT might not be set and is initailized as a setting with a default value in the container. It can also be initialized via APP_CONF_OVERRIDE
|
||||
@@ -71,4 +73,4 @@ else
|
||||
echo "[HEALTHCHECK] ❌ One or more health checks failed"
|
||||
fi
|
||||
|
||||
exit $EXIT_CODE
|
||||
exit $EXIT_CODE
|
||||
|
||||
@@ -20,6 +20,7 @@ nav:
|
||||
- Docker Updates: UPDATES.md
|
||||
- Docker Maintenance: DOCKER_MAINTENANCE.md
|
||||
- Docker Startup Troubleshooting:
|
||||
- ARP flux sysctls: docker-troubleshooting/arp-flux-sysctls.md
|
||||
- Aufs capabilities: docker-troubleshooting/aufs-capabilities.md
|
||||
- Excessive capabilities: docker-troubleshooting/excessive-capabilities.md
|
||||
- File permissions: docker-troubleshooting/file-permissions.md
|
||||
|
||||
@@ -85,7 +85,7 @@ class Device(ObjectType):
|
||||
devStatus = String(description="Online/Offline status")
|
||||
devIsRandomMac = Int(description="Calculated: Is MAC address randomized?")
|
||||
devParentChildrenCount = Int(description="Calculated: Number of children attached to this parent")
|
||||
devIpLong = Int(description="Calculated: IP address in long format")
|
||||
devIpLong = String(description="Calculated: IP address in long format (returned as string to support the full unsigned 32-bit range)")
|
||||
devFilterStatus = String(description="Calculated: Device status for UI filtering")
|
||||
devFQDN = String(description="Fully Qualified Domain Name")
|
||||
devParentRelType = String(description="Relationship type to parent")
|
||||
@@ -200,13 +200,26 @@ class Query(ObjectType):
|
||||
mylog("none", f"[graphql_schema] Error loading devices data: {e}")
|
||||
return DeviceResult(devices=[], count=0)
|
||||
|
||||
# Int fields that may arrive from the DB as empty strings — coerce to None
|
||||
_INT_FIELDS = [
|
||||
"devFavorite", "devStaticIP", "devScan", "devLogEvents", "devAlertEvents",
|
||||
"devAlertDown", "devSkipRepeated", "devPresentLastScan", "devIsNew",
|
||||
"devIsArchived", "devReqNicsOnline", "devFlapping", "devCanSleep", "devIsSleeping",
|
||||
]
|
||||
|
||||
# Add dynamic fields to each device
|
||||
for device in devices_data:
|
||||
device["devIsRandomMac"] = 1 if is_random_mac(device["devMac"]) else 0
|
||||
device["devParentChildrenCount"] = get_number_of_children(
|
||||
device["devMac"], devices_data
|
||||
)
|
||||
device["devIpLong"] = format_ip_long(device.get("devLastIP", ""))
|
||||
# Return as string — IPv4 long values can exceed Int's signed 32-bit max (2,147,483,647)
|
||||
device["devIpLong"] = str(format_ip_long(device.get("devLastIP", "")))
|
||||
|
||||
# Coerce empty strings to None so GraphQL Int serialisation doesn't fail
|
||||
for _field in _INT_FIELDS:
|
||||
if device.get(_field) == "":
|
||||
device[_field] = None
|
||||
|
||||
mylog("trace", f"[graphql_schema] devices_data: {devices_data}")
|
||||
|
||||
@@ -550,7 +563,7 @@ class Query(ObjectType):
|
||||
langStrings = []
|
||||
|
||||
# --- CORE JSON FILES ---
|
||||
language_folder = '/app/front/php/templates/language/'
|
||||
language_folder = '/app/front/php/templates/language/language_definitions/'
|
||||
if os.path.exists(language_folder):
|
||||
for filename in os.listdir(language_folder):
|
||||
if filename.endswith('.json') and filename != 'languages.json':
|
||||
|
||||
@@ -7,7 +7,7 @@ from logger import mylog
|
||||
|
||||
INSTALL_PATH = os.getenv("NETALERTX_APP", "/app")
|
||||
LANGUAGES_JSON_PATH = os.path.join(
|
||||
INSTALL_PATH, "front", "php", "templates", "language", "languages.json"
|
||||
INSTALL_PATH, "front", "php", "templates", "language", "language_definitions", "languages.json"
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ from messaging.in_app import write_notification
|
||||
# ===============================================================================
|
||||
|
||||
_LANGUAGES_JSON = os.path.join(
|
||||
applicationPath, "front", "php", "templates", "language", "languages.json"
|
||||
applicationPath, "front", "php", "templates", "language", "language_definitions" ,"languages.json"
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -115,24 +115,19 @@ def is_datetime_future(dt, current_threshold=None):
|
||||
return dt > current_threshold
|
||||
|
||||
|
||||
def ensure_future_datetime(schedule_obj, current_threshold=None, max_retries=5):
|
||||
def ensure_future_datetime(schedule_obj, current_threshold=None):
|
||||
"""
|
||||
Ensure a schedule's next() call returns a datetime strictly in the future.
|
||||
|
||||
This is a defensive utility for cron/schedule libraries that should always return
|
||||
future times but may have edge cases. Validates and retries if needed.
|
||||
Keeps calling .next() until a future time is returned — never raises.
|
||||
|
||||
Args:
|
||||
schedule_obj: A schedule object with a .next() method (e.g., from croniter/APScheduler)
|
||||
current_threshold: datetime to compare against. If None, uses timeNowTZ(as_string=False)
|
||||
max_retries: Maximum times to call .next() if result is not in future (default: 5)
|
||||
|
||||
Returns:
|
||||
datetime.datetime: A guaranteed future datetime from schedule_obj.next()
|
||||
|
||||
Raises:
|
||||
RuntimeError: If max_retries exceeded without getting a future time
|
||||
|
||||
Examples:
|
||||
newSchedule = Cron(run_sch).schedule(start_date=timeNowUTC(as_string=False))
|
||||
next_time = ensure_future_datetime(newSchedule)
|
||||
@@ -141,17 +136,9 @@ def ensure_future_datetime(schedule_obj, current_threshold=None, max_retries=5):
|
||||
current_threshold = timeNowTZ(as_string=False)
|
||||
|
||||
next_time = schedule_obj.next()
|
||||
retries = 0
|
||||
|
||||
while next_time <= current_threshold and retries < max_retries:
|
||||
while next_time <= current_threshold:
|
||||
next_time = schedule_obj.next()
|
||||
retries += 1
|
||||
|
||||
if next_time <= current_threshold:
|
||||
raise RuntimeError(
|
||||
f"[ensure_future_datetime] Failed to get future time after {max_retries} retries. "
|
||||
f"Last attempt: {next_time}, Current time: {current_threshold}"
|
||||
)
|
||||
|
||||
return next_time
|
||||
|
||||
|
||||
@@ -43,6 +43,10 @@ def create_dummy(client, api_token, test_mac):
|
||||
client.post(f"/device/{test_mac}", json=payload, headers=auth_headers(api_token))
|
||||
|
||||
|
||||
def delete_dummy(client, api_token, test_mac):
|
||||
client.delete("/devices", json={"macs": [test_mac]}, headers=auth_headers(api_token))
|
||||
|
||||
|
||||
def test_get_all_devices(client, api_token, test_mac):
|
||||
# Ensure there is at least one device
|
||||
create_dummy(client, api_token, test_mac)
|
||||
@@ -149,53 +153,62 @@ def test_export_import_cycle_base64(client, api_token, test_mac):
|
||||
|
||||
|
||||
def test_devices_totals(client, api_token, test_mac):
|
||||
# 1. Create a dummy device
|
||||
create_dummy(client, api_token, test_mac)
|
||||
try:
|
||||
# 1. Call the totals endpoint
|
||||
resp = client.get("/devices/totals", headers=auth_headers(api_token))
|
||||
assert resp.status_code == 200
|
||||
|
||||
# 2. Call the totals endpoint
|
||||
resp = client.get("/devices/totals", headers=auth_headers(api_token))
|
||||
assert resp.status_code == 200
|
||||
# 2. Ensure the response is a JSON list
|
||||
data = resp.json
|
||||
assert isinstance(data, list)
|
||||
|
||||
# 3. Ensure the response is a JSON list
|
||||
data = resp.json
|
||||
assert isinstance(data, list)
|
||||
# 3. Dynamically get expected length
|
||||
conditions = get_device_conditions()
|
||||
expected_length = len(conditions)
|
||||
assert len(data) == expected_length
|
||||
|
||||
# 4. Dynamically get expected length
|
||||
conditions = get_device_conditions()
|
||||
expected_length = len(conditions)
|
||||
assert len(data) == expected_length
|
||||
|
||||
# 5. Check that at least 1 device exists
|
||||
assert data[0] >= 1 # 'devices' count includes the dummy device
|
||||
# 4. Check that at least 1 device exists when there are any conditions
|
||||
if expected_length > 0:
|
||||
assert data[0] >= 1 # 'devices' count includes the dummy device
|
||||
else:
|
||||
# no conditions defined; data should be an empty list
|
||||
assert data == []
|
||||
finally:
|
||||
delete_dummy(client, api_token, test_mac)
|
||||
|
||||
|
||||
def test_devices_by_status(client, api_token, test_mac):
|
||||
# 1. Create a dummy device
|
||||
create_dummy(client, api_token, test_mac)
|
||||
try:
|
||||
# 1. Request devices by a valid status
|
||||
resp = client.get("/devices/by-status?status=my", headers=auth_headers(api_token))
|
||||
assert resp.status_code == 200
|
||||
data = resp.json
|
||||
assert isinstance(data, list)
|
||||
assert any(d["id"] == test_mac for d in data)
|
||||
|
||||
# 2. Request devices by a valid status
|
||||
resp = client.get("/devices/by-status?status=my", headers=auth_headers(api_token))
|
||||
assert resp.status_code == 200
|
||||
data = resp.json
|
||||
assert isinstance(data, list)
|
||||
assert any(d["id"] == test_mac for d in data)
|
||||
# 2. Request devices with an invalid/unknown status
|
||||
resp_invalid = client.get("/devices/by-status?status=invalid_status", headers=auth_headers(api_token))
|
||||
# Strict validation now returns 422 for invalid status enum values
|
||||
assert resp_invalid.status_code == 422
|
||||
|
||||
# 3. Request devices with an invalid/unknown status
|
||||
resp_invalid = client.get("/devices/by-status?status=invalid_status", headers=auth_headers(api_token))
|
||||
# Strict validation now returns 422 for invalid status enum values
|
||||
assert resp_invalid.status_code == 422
|
||||
# 3. Check favorite formatting if devFavorite = 1
|
||||
# Update dummy device to favorite
|
||||
update_resp = client.post(
|
||||
f"/device/{test_mac}",
|
||||
json={"devFavorite": 1},
|
||||
headers=auth_headers(api_token)
|
||||
)
|
||||
assert update_resp.status_code == 200
|
||||
assert update_resp.json.get("success") is True
|
||||
|
||||
# 4. Check favorite formatting if devFavorite = 1
|
||||
# Update dummy device to favorite
|
||||
client.post(
|
||||
f"/device/{test_mac}",
|
||||
json={"devFavorite": 1},
|
||||
headers=auth_headers(api_token)
|
||||
)
|
||||
resp_fav = client.get("/devices/by-status?status=my", headers=auth_headers(api_token))
|
||||
fav_data = next((d for d in resp_fav.json if d["id"] == test_mac), None)
|
||||
assert fav_data is not None
|
||||
assert "★" in fav_data["title"]
|
||||
resp_fav = client.get("/devices/by-status?status=my", headers=auth_headers(api_token))
|
||||
fav_data = next((d for d in resp_fav.json if d["id"] == test_mac), None)
|
||||
assert fav_data is not None
|
||||
assert "★" in fav_data["title"]
|
||||
finally:
|
||||
delete_dummy(client, api_token, test_mac)
|
||||
|
||||
|
||||
def test_delete_test_devices(client, api_token):
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import pytest
|
||||
from unittest.mock import patch, MagicMock
|
||||
from datetime import datetime
|
||||
import random
|
||||
|
||||
from api_server.api_server_start import app
|
||||
from helper import get_setting_value
|
||||
@@ -21,6 +22,31 @@ def auth_headers(token):
|
||||
return {"Authorization": f"Bearer {token}"}
|
||||
|
||||
|
||||
def create_dummy(client, api_token, test_mac):
|
||||
payload = {
|
||||
"createNew": True,
|
||||
"devName": "Test Device MCP",
|
||||
"devOwner": "Unit Test",
|
||||
"devType": "Router",
|
||||
"devVendor": "TestVendor",
|
||||
}
|
||||
response = client.post(f"/device/{test_mac}", json=payload, headers=auth_headers(api_token))
|
||||
assert response.status_code in [200, 201], (
|
||||
f"Expected status 200/201 for device creation, got {response.status_code}. "
|
||||
f"Response body: {response.get_data(as_text=True)}"
|
||||
)
|
||||
return response
|
||||
|
||||
|
||||
def delete_dummy(client, api_token, test_mac):
|
||||
response = client.delete("/devices", json={"macs": [test_mac]}, headers=auth_headers(api_token))
|
||||
assert response.status_code == 200, (
|
||||
f"Expected status 200 for device deletion, got {response.status_code}. "
|
||||
f"Response body: {response.get_data(as_text=True)}"
|
||||
)
|
||||
return response
|
||||
|
||||
|
||||
# --- Device Search Tests ---
|
||||
|
||||
|
||||
@@ -350,25 +376,22 @@ def test_mcp_devices_import_json(mock_db_conn, client, api_token):
|
||||
# --- MCP Device Totals Tests ---
|
||||
|
||||
|
||||
@patch("database.get_temp_db_connection")
|
||||
def test_mcp_devices_totals(mock_db_conn, client, api_token):
|
||||
def test_mcp_devices_totals(client, api_token):
|
||||
"""Test MCP devices totals endpoint."""
|
||||
mock_conn = MagicMock()
|
||||
mock_sql = MagicMock()
|
||||
mock_execute_result = MagicMock()
|
||||
# Mock the getTotals method to return sample data
|
||||
mock_execute_result.fetchone.return_value = [10, 8, 2, 0, 1, 3] # devices, connected, favorites, new, down, archived
|
||||
mock_sql.execute.return_value = mock_execute_result
|
||||
mock_conn.cursor.return_value = mock_sql
|
||||
mock_db_conn.return_value = mock_conn
|
||||
test_mac = "aa:bb:cc:" + ":".join(f"{random.randint(0, 255):02X}" for _ in range(3)).lower()
|
||||
create_dummy(client, api_token, test_mac)
|
||||
|
||||
response = client.get("/devices/totals", headers=auth_headers(api_token))
|
||||
try:
|
||||
response = client.get("/devices/totals", headers=auth_headers(api_token))
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.get_json()
|
||||
# Should return device counts as array
|
||||
assert isinstance(data, list)
|
||||
assert len(data) >= 4 # At least online, offline, etc.
|
||||
assert response.status_code == 200
|
||||
data = response.get_json()
|
||||
# Should return device counts as array
|
||||
assert isinstance(data, list)
|
||||
assert len(data) >= 4 # At least online, offline, etc.
|
||||
assert data[0] >= 1
|
||||
finally:
|
||||
delete_dummy(client, api_token, test_mac)
|
||||
|
||||
|
||||
# --- MCP Traceroute Tests ---
|
||||
|
||||
@@ -317,14 +317,18 @@ def _select_custom_ports(exclude: set[int] | None = None) -> int:
|
||||
raise RuntimeError("Unable to locate a free high port for compose testing")
|
||||
|
||||
|
||||
def _make_port_check_hook(ports: tuple[int, ...]) -> Callable[[], None]:
|
||||
def _make_port_check_hook(
|
||||
ports: tuple[int, ...],
|
||||
settle_wait_seconds: int = COMPOSE_SETTLE_WAIT_SECONDS,
|
||||
port_wait_timeout: int = COMPOSE_PORT_WAIT_TIMEOUT,
|
||||
) -> Callable[[], None]:
|
||||
"""Return a callback that waits for the provided ports to accept TCP connections."""
|
||||
|
||||
def _hook() -> None:
|
||||
for port in ports:
|
||||
LAST_PORT_SUCCESSES.pop(port, None)
|
||||
time.sleep(COMPOSE_SETTLE_WAIT_SECONDS)
|
||||
_wait_for_ports(ports, timeout=COMPOSE_PORT_WAIT_TIMEOUT)
|
||||
time.sleep(settle_wait_seconds)
|
||||
_wait_for_ports(ports, timeout=port_wait_timeout)
|
||||
|
||||
return _hook
|
||||
|
||||
@@ -344,6 +348,7 @@ def _write_normal_startup_compose(
|
||||
|
||||
service_env = service.setdefault("environment", {})
|
||||
service_env.setdefault("NETALERTX_CHECK_ONLY", "1")
|
||||
service_env.setdefault("SKIP_STARTUP_CHECKS", "host optimization")
|
||||
|
||||
if env_overrides:
|
||||
service_env.update(env_overrides)
|
||||
@@ -852,12 +857,18 @@ def test_normal_startup_no_warnings_compose(tmp_path: pathlib.Path) -> None:
|
||||
default_project = "netalertx-normal-default"
|
||||
|
||||
default_compose_file = _write_normal_startup_compose(default_dir, default_project, default_env_overrides)
|
||||
port_check_timeout = 20
|
||||
settle_wait_seconds = 2
|
||||
default_result = _run_docker_compose(
|
||||
default_compose_file,
|
||||
default_project,
|
||||
timeout=8,
|
||||
detached=True,
|
||||
post_up=_make_port_check_hook(default_ports),
|
||||
post_up=_make_port_check_hook(
|
||||
default_ports,
|
||||
settle_wait_seconds=settle_wait_seconds,
|
||||
port_wait_timeout=port_check_timeout,
|
||||
),
|
||||
)
|
||||
# MANDATORY LOGGING - DO NOT REMOVE (see file header for reasoning)
|
||||
print("\n[compose output default]", default_result.output)
|
||||
@@ -885,9 +896,14 @@ def test_normal_startup_no_warnings_compose(tmp_path: pathlib.Path) -> None:
|
||||
f"Unexpected mount row values for /data: {data_parts[2:4]}"
|
||||
)
|
||||
|
||||
allowed_warning = "⚠️ WARNING: ARP flux sysctls are not set."
|
||||
|
||||
assert "Write permission denied" not in default_output
|
||||
assert "CRITICAL" not in default_output
|
||||
assert "⚠️" not in default_output
|
||||
assert all(
|
||||
"⚠️" not in line or allowed_warning in line
|
||||
for line in default_output.splitlines()
|
||||
), "Unexpected warning found in default output"
|
||||
|
||||
custom_http = _select_custom_ports({default_http_port})
|
||||
custom_graphql = _select_custom_ports({default_http_port, custom_http})
|
||||
@@ -913,7 +929,11 @@ def test_normal_startup_no_warnings_compose(tmp_path: pathlib.Path) -> None:
|
||||
custom_project,
|
||||
timeout=8,
|
||||
detached=True,
|
||||
post_up=_make_port_check_hook(custom_ports),
|
||||
post_up=_make_port_check_hook(
|
||||
custom_ports,
|
||||
settle_wait_seconds=settle_wait_seconds,
|
||||
port_wait_timeout=port_check_timeout,
|
||||
),
|
||||
)
|
||||
print("\n[compose output custom]", custom_result.output)
|
||||
custom_output = _assert_ports_ready(custom_result, custom_project, custom_ports)
|
||||
@@ -922,8 +942,16 @@ def test_normal_startup_no_warnings_compose(tmp_path: pathlib.Path) -> None:
|
||||
assert "❌" not in custom_output
|
||||
assert "Write permission denied" not in custom_output
|
||||
assert "CRITICAL" not in custom_output
|
||||
assert "⚠️" not in custom_output
|
||||
lowered_custom = custom_output.lower()
|
||||
assert all(
|
||||
"⚠️" not in line or allowed_warning in line
|
||||
for line in custom_output.splitlines()
|
||||
), "Unexpected warning found in custom output"
|
||||
custom_output_without_allowed_warning = "\n".join(
|
||||
line
|
||||
for line in custom_output.splitlines()
|
||||
if allowed_warning.lower() not in line.lower()
|
||||
)
|
||||
lowered_custom = custom_output_without_allowed_warning.lower()
|
||||
assert "arning" not in lowered_custom
|
||||
assert "rror" not in lowered_custom
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ such as environment variable settings and check skipping.
|
||||
import subprocess
|
||||
import uuid
|
||||
import pytest
|
||||
import shutil
|
||||
|
||||
IMAGE = "netalertx-test"
|
||||
|
||||
@@ -85,8 +86,49 @@ def test_no_app_conf_override_when_no_graphql_port():
|
||||
|
||||
def test_skip_startup_checks_env_var():
|
||||
# If SKIP_STARTUP_CHECKS contains the human-readable name of a check (e.g. "mandatory folders"),
|
||||
# the entrypoint should skip that specific check. We check that the "Creating NetAlertX log directory."
|
||||
# the entrypoint should skip that specific check. We check that the "Creating NetAlertX log directory."
|
||||
# message (from the mandatory folders check) is not printed when skipped.
|
||||
result = _run_entrypoint(env={"SKIP_STARTUP_CHECKS": "mandatory folders"}, check_only=True)
|
||||
assert "Creating NetAlertX log directory" not in result.stdout
|
||||
assert result.returncode == 0
|
||||
|
||||
|
||||
@pytest.mark.docker
|
||||
@pytest.mark.feature_complete
|
||||
def test_host_optimization_warning_matches_sysctl():
|
||||
"""Validate host-optimization warning matches actual host sysctl values."""
|
||||
sysctl_bin = shutil.which("sysctl")
|
||||
if not sysctl_bin:
|
||||
pytest.skip("sysctl binary not found on host; skipping host-optimization warning check")
|
||||
|
||||
ignore_proc = subprocess.run(
|
||||
[sysctl_bin, "-n", "net.ipv4.conf.all.arp_ignore"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=False,
|
||||
timeout=10,
|
||||
)
|
||||
announce_proc = subprocess.run(
|
||||
[sysctl_bin, "-n", "net.ipv4.conf.all.arp_announce"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=False,
|
||||
timeout=10,
|
||||
)
|
||||
|
||||
if ignore_proc.returncode != 0 or announce_proc.returncode != 0:
|
||||
pytest.skip("sysctl values unavailable on host; skipping host-optimization warning check")
|
||||
|
||||
arp_ignore = ignore_proc.stdout.strip()
|
||||
arp_announce = announce_proc.stdout.strip()
|
||||
expected_warning = not (arp_ignore == "1" and arp_announce == "2")
|
||||
|
||||
result = _run_entrypoint(check_only=True)
|
||||
combined_output = result.stdout + result.stderr
|
||||
warning_present = "WARNING: ARP flux sysctls are not set." in combined_output
|
||||
|
||||
assert warning_present == expected_warning, (
|
||||
"host-optimization warning mismatch: "
|
||||
f"arp_ignore={arp_ignore}, arp_announce={arp_announce}, "
|
||||
f"expected_warning={expected_warning}, warning_present={warning_present}"
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user