mirror of
https://github.com/jokob-sk/NetAlertX.git
synced 2025-12-06 17:15:38 -08:00
Merge remote-tracking branch 'origin/main' into hardening
This commit is contained in:
@@ -512,21 +512,29 @@ function updateDevicePageName(mac) {
|
||||
}
|
||||
|
||||
// Page title - Name
|
||||
if (mac == "new") {
|
||||
$('#pageTitle').html(
|
||||
`<i title="${getString("Gen_create_new_device")}" class="fa fa-square-plus"></i> ` + getString("Gen_create_new_device")
|
||||
);
|
||||
$('#devicePageInfoPlc .inner').html(
|
||||
`<i class="fa fa-circle-info"></i> ` + getString("Gen_create_new_device_info")
|
||||
);
|
||||
$('#devicePageInfoPlc').show();
|
||||
} else if (!owner || (name.toString()).indexOf(owner) !== -1) {
|
||||
$('#pageTitle').html(name);
|
||||
$('#devicePageInfoPlc').hide();
|
||||
let pageTitleText;
|
||||
|
||||
if (mac === "new") {
|
||||
pageTitleText = getString("Gen_create_new_device");
|
||||
$('#pageTitle').html(
|
||||
`<i title="${pageTitleText}" class="fa fa-square-plus"></i> ` + pageTitleText
|
||||
);
|
||||
$('#devicePageInfoPlc .inner').html(
|
||||
`<i class="fa fa-circle-info"></i> ` + getString("Gen_create_new_device_info")
|
||||
);
|
||||
$('#devicePageInfoPlc').show();
|
||||
} else if (!owner || name.toString().includes(owner)) {
|
||||
pageTitleText = name;
|
||||
$('#pageTitle').html(pageTitleText);
|
||||
$('#devicePageInfoPlc').hide();
|
||||
} else {
|
||||
$('#pageTitle').html(name + ' (' + owner + ')');
|
||||
$('#devicePageInfoPlc').hide();
|
||||
pageTitleText = `${name} (${owner})`;
|
||||
$('#pageTitle').html(pageTitleText);
|
||||
$('#devicePageInfoPlc').hide();
|
||||
}
|
||||
|
||||
// Prepend to the <title> tag
|
||||
$('title').html(pageTitleText + ' - ' + $('title').html());
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -123,7 +123,7 @@
|
||||
<!-- page script ----------------------------------------------------------- -->
|
||||
<script>
|
||||
var deviceStatus = 'all';
|
||||
var tableRows = getCache ("nax_parTableRows") == "" ? 20 : getCache ("nax_parTableRows") ;
|
||||
var tableRows = getCache ("nax_parTableRows") == "" ? parseInt(getSetting("UI_DEFAULT_PAGE_SIZE")) : getCache ("nax_parTableRows") ;
|
||||
var tableOrder = getCache ("nax_parTableOrder") == "" ? [[3,'desc'], [0,'asc']] : JSON.parse(getCache ("nax_parTableOrder")) ;
|
||||
|
||||
var tableColumnHide = [];
|
||||
@@ -743,7 +743,7 @@ function initializeDatatable (status) {
|
||||
},
|
||||
'paging' : true,
|
||||
'lengthChange' : true,
|
||||
'lengthMenu' : [[10, 20, 25, 50, 100, 500, 100000], [10, 20, 25, 50, 100, 500, getString('Device_Tablelenght_all')]],
|
||||
'lengthMenu' : getLengthMenu(parseInt(getSetting("UI_DEFAULT_PAGE_SIZE"))),
|
||||
'searching' : true,
|
||||
|
||||
'ordering' : true,
|
||||
|
||||
@@ -169,7 +169,7 @@
|
||||
|
||||
var eventsType = 'all';
|
||||
var period = '1 day';
|
||||
var tableRows = 25;
|
||||
var tableRows = parseInt(getSetting("UI_DEFAULT_PAGE_SIZE"));
|
||||
|
||||
// Read parameters & Initialize components
|
||||
main();
|
||||
@@ -181,7 +181,7 @@ function main() {
|
||||
period = getCookie(parPeriod) === "" ? "1 day" : getCookie(parPeriod);
|
||||
$('#period').val(period);
|
||||
|
||||
tableRows = getCookie(parTableRows) === "" ? 50 : parseInt(getCookie(parTableRows), 10);
|
||||
tableRows = getCookie(parTableRows) === "" ? parseInt(getSetting("UI_DEFAULT_PAGE_SIZE")) : parseInt(getCookie(parTableRows), 10);
|
||||
|
||||
// Initialize components
|
||||
initializeDatatable();
|
||||
@@ -197,7 +197,7 @@ function initializeDatatable () {
|
||||
$('#tableEvents').DataTable({
|
||||
'paging' : true,
|
||||
'lengthChange' : true,
|
||||
'lengthMenu' : [[10, 25, 50, 100, 500, -1], [10, 25, 50, 100, 500, 'All']],
|
||||
'lengthMenu' : getLengthMenu(parseInt(getSetting("UI_DEFAULT_PAGE_SIZE"))),
|
||||
'searching' : true,
|
||||
'ordering' : true,
|
||||
'info' : true,
|
||||
|
||||
@@ -11,8 +11,8 @@
|
||||
var timerRefreshData = ''
|
||||
|
||||
var emptyArr = ['undefined', "", undefined, null, 'null'];
|
||||
var UI_LANG = "English";
|
||||
const allLanguages = ["en_us", "es_es", "de_de", "fr_fr", "it_it", "ru_ru", "nb_no", "pl_pl", "pt_br", "pt_pt", "tr_tr", "zh_cn", "cs_cz", "ar_ar", "ca_ca", "uk_ua"]; // needs to be same as in lang.php
|
||||
var UI_LANG = "English (en_us)";
|
||||
const allLanguages = ["ar_ar","ca_ca","cs_cz","de_de","en_us","es_es","fa_fa","fr_fr","it_it","nb_no","pl_pl","pt_br","pt_pt","ru_ru","tr_tr","uk_ua","zh_cn"]; // needs to be same as in lang.php
|
||||
var settingsJSON = {}
|
||||
|
||||
|
||||
@@ -299,7 +299,7 @@ function getString(key) {
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Get current language ISO code
|
||||
// below has to match exactly teh values in /front/php/templates/language/lang.php & /front/js/common.js
|
||||
// below has to match exactly the values in /front/php/templates/language/lang.php & /front/js/common.js
|
||||
function getLangCode() {
|
||||
|
||||
UI_LANG = getSetting("UI_LANG");
|
||||
@@ -307,19 +307,22 @@ function getLangCode() {
|
||||
let lang_code = 'en_us';
|
||||
|
||||
switch (UI_LANG) {
|
||||
case 'English':
|
||||
case 'English (en_us)':
|
||||
lang_code = 'en_us';
|
||||
break;
|
||||
case 'Spanish':
|
||||
case 'Spanish (es_es)':
|
||||
lang_code = 'es_es';
|
||||
break;
|
||||
case 'German':
|
||||
case 'German (de_de)':
|
||||
lang_code = 'de_de';
|
||||
break;
|
||||
case 'French':
|
||||
case 'Farsi (fa_fa)':
|
||||
lang_code = 'fa_fa';
|
||||
break;
|
||||
case 'French (fr_fr)':
|
||||
lang_code = 'fr_fr';
|
||||
break;
|
||||
case 'Norwegian':
|
||||
case 'Norwegian (nb_no)':
|
||||
lang_code = 'nb_no';
|
||||
break;
|
||||
case 'Polish (pl_pl)':
|
||||
@@ -337,7 +340,7 @@ function getLangCode() {
|
||||
case 'Italian (it_it)':
|
||||
lang_code = 'it_it';
|
||||
break;
|
||||
case 'Russian':
|
||||
case 'Russian (ru_ru)':
|
||||
lang_code = 'ru_ru';
|
||||
break;
|
||||
case 'Chinese (zh_cn)':
|
||||
|
||||
@@ -497,26 +497,78 @@ function checkNotification() {
|
||||
});
|
||||
}
|
||||
|
||||
// Handling unread notifications favicon + bell floating number bublbe
|
||||
/**
|
||||
* Handles unread notification indicators:
|
||||
* - Updates the floating bell count bubble.
|
||||
* - Changes the favicon to indicate unread notifications.
|
||||
* - Updates the page title with a numeric prefix like "(3)".
|
||||
*
|
||||
* The function expects that the favicon element has the ID `#favicon`
|
||||
* and that the bell count element has the ID `#unread-notifications-bell-count`.
|
||||
*
|
||||
* @param {number} count - The number of unread notifications.
|
||||
*
|
||||
* @example
|
||||
* handleUnreadNotifications(3);
|
||||
* // → shows "(3)" in the title, notification icon, and bell bubble
|
||||
*
|
||||
* handleUnreadNotifications(0);
|
||||
* // → restores original favicon and hides bubble
|
||||
*/
|
||||
function handleUnreadNotifications(count) {
|
||||
$('#unread-notifications-bell-count').html(count);
|
||||
const $countBubble = $('#unread-notifications-bell-count');
|
||||
const $favicon = $('#favicon');
|
||||
|
||||
// Capture current title — ideally cache the original globally if calling repeatedly
|
||||
const originalTitle = document.title;
|
||||
|
||||
// Update notification bubble and favicon
|
||||
$countBubble.html(count);
|
||||
if (count > 0) {
|
||||
$('#unread-notifications-bell-count').show();
|
||||
// Change the favicon to show there are notifications
|
||||
$('#favicon').attr('href', 'img/NetAlertX_logo_notification.png');
|
||||
// Update the title to include the count
|
||||
document.title = `(${count}) ` + originalTitle;
|
||||
$countBubble.show();
|
||||
$favicon.attr('href', 'img/NetAlertX_logo_notification.png');
|
||||
} else {
|
||||
$('#unread-notifications-bell-count').hide();
|
||||
// Change the favicon back to the original
|
||||
$('#favicon').attr('href', 'img/NetAlertX_logo.png');
|
||||
// Revert the title to the original title
|
||||
document.title = originalTitle;
|
||||
$countBubble.hide();
|
||||
$favicon.attr('href', 'img/NetAlertX_logo.png');
|
||||
}
|
||||
|
||||
// Update the document title with "(count)" prefix
|
||||
document.title = addOrUpdateNumberBrackets(originalTitle, count);
|
||||
}
|
||||
|
||||
// Store the original title of the document
|
||||
var originalTitle = document.title;
|
||||
/**
|
||||
* Adds, updates, or removes a numeric prefix in parentheses before a given string.
|
||||
*
|
||||
* Behavior:
|
||||
* - If `count` is 0 → removes any existing "(...)" prefix.
|
||||
* - If string already starts with "(...)" → replaces it with the new count.
|
||||
* - Otherwise → adds "(count)" as a prefix before the input text.
|
||||
*
|
||||
* Examples:
|
||||
* addOrUpdateNumberBrackets("Device", 3) → "(3) Device"
|
||||
* addOrUpdateNumberBrackets("(1) Device", 4) → "(4) Device"
|
||||
* addOrUpdateNumberBrackets("(5) Device", 0) → "Device"
|
||||
*
|
||||
* @param {string} input - The input string (e.g., a device name).
|
||||
* @param {number} count - The number to place inside the parentheses.
|
||||
* @returns {string} The updated string with the correct "(count)" prefix.
|
||||
*/
|
||||
function addOrUpdateNumberBrackets(input, count) {
|
||||
let result = input.trim();
|
||||
|
||||
if (count === 0) {
|
||||
// Remove any existing "(...)" prefix
|
||||
result = result.replace(/^\(.*?\)\s*/, '');
|
||||
} else if (/^\(.*?\)/.test(result)) {
|
||||
// Replace existing "(...)" prefix
|
||||
result = result.replace(/^\(.*?\)/, `(${count})`);
|
||||
} else {
|
||||
// Add new "(count)" prefix
|
||||
result = `(${count}) ${result}`;
|
||||
}
|
||||
|
||||
return result.trim();
|
||||
}
|
||||
|
||||
|
||||
// Start checking for notifications periodically
|
||||
|
||||
@@ -952,5 +952,41 @@ function initHoverNodeInfo() {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a DataTables-style `lengthMenu` array with an optional custom entry inserted
|
||||
* in the correct numeric order.
|
||||
*
|
||||
* Example output:
|
||||
* [[10, 20, 25, 50, 100, 500, 100000], [10, 20, 25, 50, 100, 500, 'All']]
|
||||
*
|
||||
* @param {number} newEntry - A numeric entry to insert into the list (e.g. 30).
|
||||
* If it already exists or equals -1, it will be ignored.
|
||||
* @returns {Array[]} A two-dimensional array where:
|
||||
* - The first array is the numeric page lengths.
|
||||
* - The second array is the display labels (same values, but 'All' for -1).
|
||||
*
|
||||
* @example
|
||||
* getLengthMenu(30);
|
||||
* // → [[10, 20, 25, 30, 50, 100, 500, 100000], [10, 20, 25, 30, 50, 100, 500, 'All']]
|
||||
*/
|
||||
function getLengthMenu(newEntry) {
|
||||
const values = [10, 20, 25, 50, 100, 500, 100000];
|
||||
const labels = [10, 20, 25, 50, 100, 500, getString('Device_Tablelenght_all')];
|
||||
|
||||
// Insert newEntry in sorted order, skipping duplicates and -1/'All'
|
||||
const insertSorted = (arr, val) => {
|
||||
if (val === -1 || arr.includes(val)) return arr;
|
||||
const idx = arr.findIndex(v => v > val || v === -1);
|
||||
if (idx === -1) arr.push(val);
|
||||
else arr.splice(idx, 0, val);
|
||||
return arr;
|
||||
};
|
||||
|
||||
insertSorted(values, newEntry);
|
||||
insertSorted(labels, newEntry);
|
||||
|
||||
return [values, labels];
|
||||
}
|
||||
|
||||
|
||||
console.log("init ui_components.js")
|
||||
@@ -340,9 +340,14 @@
|
||||
console.log(columnValue);
|
||||
|
||||
// update selected
|
||||
executeAction('update', 'devMac', selectorMacs(), targetColumns, columnValue )
|
||||
|
||||
|
||||
if(selectorMacs() != "")
|
||||
{
|
||||
executeAction('update', 'devMac', selectorMacs(), targetColumns, columnValue )
|
||||
}
|
||||
else
|
||||
{
|
||||
showModalWarning(getString("Gen_Error"), getString('Device_MultiEdit_No_Devices'));
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
@@ -354,22 +359,23 @@
|
||||
function executeAction(action, whereColumnName, key, targetColumns, newTargetColumnValue )
|
||||
{
|
||||
$.get(`php/server/dbHelper.php?action=${action}&dbtable=Devices&columnName=${whereColumnName}&id=${key}&columns=${targetColumns}&values=${newTargetColumnValue}`, function(data) {
|
||||
// console.log(data);
|
||||
// console.log(data);
|
||||
|
||||
if (sanitize(data) == 'OK') {
|
||||
showMessage(getString('Gen_DataUpdatedUITakesTime'));
|
||||
// Remove navigation prompt "Are you sure you want to leave..."
|
||||
window.onbeforeunload = null;
|
||||
if (sanitize(data) == 'OK') {
|
||||
showMessage(getString('Gen_DataUpdatedUITakesTime'));
|
||||
// Remove navigation prompt "Are you sure you want to leave..."
|
||||
window.onbeforeunload = null;
|
||||
|
||||
// update API endpoints to refresh the UI
|
||||
updateApi("devices,appevents")
|
||||
// update API endpoints to refresh the UI
|
||||
updateApi("devices,appevents")
|
||||
|
||||
write_notification(`[Multi edit] Executed "${action}" on Columns "${targetColumns}" matching "${key}"`, 'info')
|
||||
write_notification(`[Multi edit] Executed "${action}" on Columns "${targetColumns}" matching "${key}"`, 'info')
|
||||
|
||||
} else {
|
||||
showMessage(getString('Gen_LockedDB'));
|
||||
}
|
||||
});
|
||||
} else {
|
||||
console.error(data);
|
||||
showMessage(getString('Gen_LockedDB'));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
<?php
|
||||
|
||||
// 🔺----- API ENDPOINTS SUPERSEDED -----🔺
|
||||
// check server/api_server/api_server_start.py for equivalents
|
||||
// equivalent: /messaging/in-app
|
||||
// 🔺----- API ENDPOINTS SUPERSEDED -----🔺
|
||||
|
||||
require dirname(__FILE__).'/../templates/globals.php';
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
@@ -199,6 +199,7 @@
|
||||
"Device_MultiEdit_Backup": "نسخة احتياطية",
|
||||
"Device_MultiEdit_Fields": "الحقول",
|
||||
"Device_MultiEdit_MassActions": "إجراءات جماعية",
|
||||
"Device_MultiEdit_No_Devices": "",
|
||||
"Device_MultiEdit_Tooltip": "تعديل الأجهزة المحددة",
|
||||
"Device_Searchbox": "بحث",
|
||||
"Device_Shortcut_AllDevices": "جميع الأجهزة",
|
||||
|
||||
@@ -199,6 +199,7 @@
|
||||
"Device_MultiEdit_Backup": "Atenció, entrar valors incorrectes a continuació trencarà la configuració. Si us plau, abans feu còpia de seguretat la vostra base de dades o configuració de Dispositius (<a href=\"php/server/devices.php?action=ExportCSV\">clic per descarregar <i class=\"fa-solid fa-download fa-bounce\"></i></a>). Llegiu com per recuperar Dispositius des d'aquest fitxer al <a href=\"https://github.com/jokob-sk/NetAlertX/blob/main/docs/BACKUPS.md#scenario-2-corrupted-database\" target=\"_blank\">documentació de Còpies de seguretat</a>. Per aplicar els canvis, feu click a la <b>Save<i class=\"fa-solid fa-save\"></i></b> icona de cada camp que volgueu actualitzar.",
|
||||
"Device_MultiEdit_Fields": "Editar camps:",
|
||||
"Device_MultiEdit_MassActions": "Accions massives:",
|
||||
"Device_MultiEdit_No_Devices": "Cap dispositiu seleccionat.",
|
||||
"Device_MultiEdit_Tooltip": "Atenció. Si feu clic a això s'aplicarà el valor de l'esquerra a tots els dispositius seleccionats a dalt.",
|
||||
"Device_Searchbox": "Cerca",
|
||||
"Device_Shortcut_AllDevices": "Els meus dispositius",
|
||||
|
||||
@@ -199,6 +199,7 @@
|
||||
"Device_MultiEdit_Backup": "",
|
||||
"Device_MultiEdit_Fields": "",
|
||||
"Device_MultiEdit_MassActions": "",
|
||||
"Device_MultiEdit_No_Devices": "",
|
||||
"Device_MultiEdit_Tooltip": "",
|
||||
"Device_Searchbox": "",
|
||||
"Device_Shortcut_AllDevices": "",
|
||||
|
||||
@@ -203,10 +203,11 @@
|
||||
"Device_MultiEdit_Backup": "Achtung! Falsche Eingaben können die Installation beschädigen. Bitte sichere deine Datenbank oder Gerätekonfiguration zuerst: (<a href=\"php/server/devices.php?action=ExportCSV\">Konfiguration herunterladen <i class=\"fa-solid fa-download fa-bounce\"></i></a>). Wie du dein Gerät wiederherstellen kannst findest du in der <a href=\"https://github.com/jokob-sk/NetAlertX/blob/main/docs/BACKUPS.md#scenario-2-corrupted-database\" target=\"_blank\">Dokumentation über Backups</a>.",
|
||||
"Device_MultiEdit_Fields": "Felder bearbeiten:",
|
||||
"Device_MultiEdit_MassActions": "Massen aktionen:",
|
||||
"Device_MultiEdit_No_Devices": "Keine Geräte ausgewählt.",
|
||||
"Device_MultiEdit_Tooltip": "Achtung! Beim Drücken werden alle Werte auf die oben ausgewählten Geräte übertragen.",
|
||||
"Device_Searchbox": "Suche",
|
||||
"Device_Shortcut_AllDevices": "Meine Geräte",
|
||||
"Device_Shortcut_AllNodes": "",
|
||||
"Device_Shortcut_AllNodes": "Alle Knoten",
|
||||
"Device_Shortcut_Archived": "Archiviert",
|
||||
"Device_Shortcut_Connected": "Verbunden",
|
||||
"Device_Shortcut_Devices": "Geräte",
|
||||
|
||||
@@ -199,6 +199,7 @@
|
||||
"Device_MultiEdit_Backup": "Careful, entering wrong values below will break your setup. Please backup your database or Devices configuration first (<a href=\"php/server/devices.php?action=ExportCSV\">click to download <i class=\"fa-solid fa-download fa-bounce\"></i></a>). Read how to recover Devices from this file in the <a href=\"https://github.com/jokob-sk/NetAlertX/blob/main/docs/BACKUPS.md#scenario-2-corrupted-database\" target=\"_blank\">Backups documentation</a>. In order to apply your changes click the <b>Save<i class=\"fa-solid fa-save\"></i></b> icon on each field you want to update.",
|
||||
"Device_MultiEdit_Fields": "Edit fields:",
|
||||
"Device_MultiEdit_MassActions": "Mass actions:",
|
||||
"Device_MultiEdit_No_Devices": "No devices selected.",
|
||||
"Device_MultiEdit_Tooltip": "Careful. Clicking this will apply the value on the left to all devices selected above.",
|
||||
"Device_Searchbox": "Search",
|
||||
"Device_Shortcut_AllDevices": "My devices",
|
||||
|
||||
@@ -201,6 +201,7 @@
|
||||
"Device_MultiEdit_Backup": "Tenga cuidado, ingresar valores incorrectos o romperá su configuración. Por favor, haga una copia de seguridad de su base de datos o de la configuración de los dispositivos primero (<a href=\"php/server/devices.php?action=ExportCSV\">haga clic para descargar <i class=\"fa-solid fa-download fa-bounce\"></i></a>). Lea cómo recuperar dispositivos de este archivo en la documentación de <a href=\"https://github.com/jokob-sk/NetAlertX/blob/main/docs/BACKUPS.md#scenario-2-corrupted-database\" target=\"_blank\">Copia de seguridad</a>. Para aplicar sus cambios haga click en el ícono de <b>Guardar<i class=\"fa-solid fa-save\"></i></b> en cada campo que quiera actualizar.",
|
||||
"Device_MultiEdit_Fields": "Editar campos:",
|
||||
"Device_MultiEdit_MassActions": "Acciones masivas:",
|
||||
"Device_MultiEdit_No_Devices": "",
|
||||
"Device_MultiEdit_Tooltip": "Cuidado. Al hacer clic se aplicará el valor de la izquierda a todos los dispositivos seleccionados anteriormente.",
|
||||
"Device_Searchbox": "Búsqueda",
|
||||
"Device_Shortcut_AllDevices": "Mis dispositivos",
|
||||
|
||||
764
front/php/templates/language/fa_fa.json
Executable file
764
front/php/templates/language/fa_fa.json
Executable file
@@ -0,0 +1,764 @@
|
||||
{
|
||||
"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": "",
|
||||
"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_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_TableHead_AlertDown": "",
|
||||
"Device_TableHead_Connected_Devices": "",
|
||||
"Device_TableHead_CustomProps": "",
|
||||
"Device_TableHead_FQDN": "",
|
||||
"Device_TableHead_Favorite": "",
|
||||
"Device_TableHead_FirstSession": "",
|
||||
"Device_TableHead_GUID": "",
|
||||
"Device_TableHead_Group": "",
|
||||
"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_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": "",
|
||||
"GRAPHQL_PORT_description": "",
|
||||
"GRAPHQL_PORT_name": "",
|
||||
"Gen_Action": "",
|
||||
"Gen_Add": "",
|
||||
"Gen_AddDevice": "",
|
||||
"Gen_Add_All": "",
|
||||
"Gen_All_Devices": "",
|
||||
"Gen_AreYouSure": "",
|
||||
"Gen_Backup": "",
|
||||
"Gen_Cancel": "",
|
||||
"Gen_Change": "",
|
||||
"Gen_Copy": "",
|
||||
"Gen_CopyToClipboard": "",
|
||||
"Gen_DataUpdatedUITakesTime": "",
|
||||
"Gen_Delete": "",
|
||||
"Gen_DeleteAll": "",
|
||||
"Gen_Description": "",
|
||||
"Gen_Error": "",
|
||||
"Gen_Filter": "",
|
||||
"Gen_Generate": "",
|
||||
"Gen_InvalidMac": "",
|
||||
"Gen_LockedDB": "",
|
||||
"Gen_NetworkMask": "",
|
||||
"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_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_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_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_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_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": "",
|
||||
"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": ""
|
||||
}
|
||||
@@ -199,6 +199,7 @@
|
||||
"Device_MultiEdit_Backup": "Attention, renseigner des valeurs non cohérentes ci-dessous peut bloquer votre paramétrage. Veillez à faire une sauvegarde de votre base de données ou de la configuration de vos appareils en premier lieu (<a href=\"php/server/devices.php?action=ExportCSV\">clisuer ici pour la télécharger <i class=\"fa-solid fa-download fa-bounce\"></i></a>). Renseignez-vous sur comment remettre les appareils depuis ce fichier via la <a href=\"https://github.com/jokob-sk/NetAlertX/blob/main/docs/BACKUPS.md#scenario-2-corrupted-database\" target=\"_blank\">documentation des sauvegardes</a>. Afin d'enregistrer les changements, cliquer sur l'icône <b>Sauvegarder<i class=\"fa-solid fa-save\"></i></b> sur chaque champ que vous voulez mettre à jour.",
|
||||
"Device_MultiEdit_Fields": "Champs modifiables :",
|
||||
"Device_MultiEdit_MassActions": "Actions en masse :",
|
||||
"Device_MultiEdit_No_Devices": "Aucun appareil sélectionné.",
|
||||
"Device_MultiEdit_Tooltip": "Attention. Ceci va appliquer la valeur de gauche à tous les appareils sélectionnés au-dessus.",
|
||||
"Device_Searchbox": "Rechercher",
|
||||
"Device_Shortcut_AllDevices": "Mes appareils",
|
||||
@@ -760,4 +761,4 @@
|
||||
"settings_system_label": "Système",
|
||||
"settings_update_item_warning": "Mettre à jour la valeur ci-dessous. Veillez à bien suivre le même format qu'auparavant. <b>Il n'y a pas de pas de contrôle.</b>",
|
||||
"test_event_tooltip": "Enregistrer d'abord vos modifications avant de tester vôtre paramétrage."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -199,6 +199,7 @@
|
||||
"Device_MultiEdit_Backup": "Attento, l'inserimento di valori errati di seguito interromperà la configurazione. Effettua prima il backup del database o della configurazione dei dispositivi (<a href=\"php/server/devices.php?action=ExportCSV\">fai clic per scaricare <i class=\"fa-solid fa-download fa-bounce\"></i> </a>). Leggi come ripristinare i dispositivi da questo file nella <a href=\"https://github.com/jokob-sk/NetAlertX/blob/main/docs/BACKUPS.md#scenario-2-corrupted-database\" target=\" _blank\">Documentazione di backup</a>. Per applicare le modifiche, fai clic sull'icona <b>Salva<i class=\"fa-solid fa-save\"></i></b> su ogni campo che desideri aggiornare.",
|
||||
"Device_MultiEdit_Fields": "Modifica campi:",
|
||||
"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_Searchbox": "Cerca",
|
||||
"Device_Shortcut_AllDevices": "I miei dispositivi",
|
||||
@@ -760,4 +761,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."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
// ###################################
|
||||
|
||||
$defaultLang = "en_us";
|
||||
$allLanguages = ["en_us", "es_es", "de_de", "fr_fr", "it_it", "ru_ru", "nb_no", "pl_pl", "pt_br", "pt_pt", "tr_tr", "zh_cn", "cs_cz", "ar_ar", "ca_ca", "uk_ua"];
|
||||
$allLanguages = [ "ar_ar", "ca_ca", "cs_cz", "de_de", "en_us", "es_es", "fa_fa", "fr_fr", "it_it", "nb_no", "pl_pl", "pt_br", "pt_pt", "ru_ru", "tr_tr", "uk_ua", "zh_cn"];
|
||||
|
||||
|
||||
global $db;
|
||||
@@ -14,22 +14,24 @@ $result = $db->querySingle("SELECT setValue FROM Settings WHERE setKey = 'UI_LAN
|
||||
|
||||
// below has to match exactly the values in /front/php/templates/language/lang.php & /front/js/common.js
|
||||
switch($result){
|
||||
case 'Spanish': $pia_lang_selected = 'es_es'; break;
|
||||
case 'German': $pia_lang_selected = 'de_de'; break;
|
||||
case 'Norwegian': $pia_lang_selected = 'nb_no'; break;
|
||||
case 'Polish (pl_pl)': $pia_lang_selected = 'pl_pl'; break;
|
||||
case 'Portuguese (pt_br)': $pia_lang_selected = 'pt_br'; break;
|
||||
case 'Portuguese (pt_pt)': $pia_lang_selected = 'pt_pt'; break;
|
||||
case 'Italian (it_it)': $pia_lang_selected = 'it_it'; break;
|
||||
case 'Russian': $pia_lang_selected = 'ru_ru'; break;
|
||||
case 'Turkish (tr_tr)': $pia_lang_selected = 'tr_tr'; break;
|
||||
case 'French': $pia_lang_selected = 'fr_fr'; break;
|
||||
case 'Chinese (zh_cn)': $pia_lang_selected = 'zh_cn'; break;
|
||||
case 'Czech (cs_cz)': $pia_lang_selected = 'cs_cz'; break;
|
||||
case 'Arabic (ar_ar)': $pia_lang_selected = 'ar_ar'; break;
|
||||
case 'Catalan (ca_ca)': $pia_lang_selected = 'ca_ca'; break;
|
||||
case 'Ukrainian (uk_ua)': $pia_lang_selected = 'uk_ua'; break;
|
||||
default: $pia_lang_selected = 'en_us'; break;
|
||||
case 'Arabic (ar_ar)': $pia_lang_selected = 'ar_ar'; break;
|
||||
case 'Catalan (ca_ca)': $pia_lang_selected = 'ca_ca'; break;
|
||||
case 'Czech (cs_cz)': $pia_lang_selected = 'cs_cz'; break;
|
||||
case 'German (de_de)': $pia_lang_selected = 'de_de'; break;
|
||||
case 'English (en_us)': $pia_lang_selected = 'en_us'; break;
|
||||
case 'Spanish (es_es)': $pia_lang_selected = 'es_es'; break;
|
||||
case 'Farsi (fa_fa)': $pia_lang_selected = 'fa_fa'; break;
|
||||
case 'French (fr_fr)': $pia_lang_selected = 'fr_fr'; break;
|
||||
case 'Italian (it_it)': $pia_lang_selected = 'it_it'; break;
|
||||
case 'Norwegian (nb_no)': $pia_lang_selected = 'nb_no'; break;
|
||||
case 'Polish (pl_pl)': $pia_lang_selected = 'pl_pl'; break;
|
||||
case 'Portuguese (pt_br)': $pia_lang_selected = 'pt_br'; break;
|
||||
case 'Portuguese (pt_pt)': $pia_lang_selected = 'pt_pt'; break;
|
||||
case 'Russian (ru_ru)': $pia_lang_selected = 'ru_ru'; break;
|
||||
case 'Turkish (tr_tr)': $pia_lang_selected = 'tr_tr'; break;
|
||||
case 'Ukrainian (uk_ua)': $pia_lang_selected = 'uk_ua'; break;
|
||||
case 'Chinese (zh_cn)': $pia_lang_selected = 'zh_cn'; break;
|
||||
default: $pia_lang_selected = 'en_us'; break;
|
||||
}
|
||||
|
||||
if (isset($pia_lang_selected) == FALSE or (strlen($pia_lang_selected) == 0)) {$pia_lang_selected = $defaultLang;}
|
||||
|
||||
@@ -33,6 +33,7 @@ def merge_translations(main_file, other_files):
|
||||
if __name__ == "__main__":
|
||||
current_path = os.path.dirname(os.path.abspath(__file__))
|
||||
# language codes can be found here: http://www.lingoes.net/en/translator/langcode.htm
|
||||
json_files = ["en_us.json", "de_de.json", "es_es.json", "fr_fr.json", "nb_no.json", "ru_ru.json", "it_it.json", "pt_br.json", "pt_pt.json", "pl_pl.json", "zh_cn.json", "tr_tr.json", "cs_cz.json", "ar_ar.json", "ca_ca.json", "uk_ua.json"]
|
||||
# "en_us.json" has to be first!
|
||||
json_files = [ "en_us.json", "ar_ar.json", "ca_ca.json", "cs_cz.json", "de_de.json", "es_es.json", "fa_fa.json", "fr_fr.json", "it_it.json", "nb_no.json", "pl_pl.json", "pt_br.json", "pt_pt.json", "ru_ru.json", "tr_tr.json", "uk_ua.json", "zh_cn.json"]
|
||||
file_paths = [os.path.join(current_path, file) for file in json_files]
|
||||
merge_translations(file_paths[0], file_paths[1:])
|
||||
|
||||
@@ -199,6 +199,7 @@
|
||||
"Device_MultiEdit_Backup": "Forsiktig, hvis du legger inn feil verdier nedenfor, vil oppsettet ditt ødelegges. Ta sikkerhetskopi av databasen eller enhetskonfigurasjonen først (<a href=\"php/server/devices.php?action=ExportCSV\">klikk for å laste ned <i class=\"fa-solid fa-download fa-bounce\"></i> </a>). Les hvordan du gjenoppretter enheter fra denne filen i <a href=\"https://github.com/jokob-sk/NetAlertX/blob/main/docs/BACKUPS.md#scenario-2-corrupted-database\" target=\"_blank\">Sikkerhetskopierings dokumentasjon</a>.",
|
||||
"Device_MultiEdit_Fields": "Rediger felt:",
|
||||
"Device_MultiEdit_MassActions": "Flerhandlinger:",
|
||||
"Device_MultiEdit_No_Devices": "",
|
||||
"Device_MultiEdit_Tooltip": "Forsiktig. Ved å klikke på denne vil verdien til venstre brukes på alle enhetene som er valgt ovenfor.",
|
||||
"Device_Searchbox": "Søk",
|
||||
"Device_Shortcut_AllDevices": "Mine Enheter",
|
||||
|
||||
@@ -199,6 +199,7 @@
|
||||
"Device_MultiEdit_Backup": "Uwaga, wprowadzenie niepoprawnych wartości poniżej może uszkodzić Twoją konfigurację. Najpierw wykonaj kopię zapasową bazy danych lub konfiguracji urządzeń (<a href=\"php/server/devices.php?action=ExportCSV\">kliknij, aby pobrać <i class=\"fa-solid fa-download fa-bounce\"></i></a>). Instrukcje odzyskiwania urządzeń z tego pliku znajdziesz w <a href=\"https://github.com/jokob-sk/NetAlertX/blob/main/docs/BACKUPS.md#scenario-2-corrupted-database\" target=\"_blank\">dokumentacji kopii zapasowych</a>. Aby zastosować zmiany, kliknij ikonę <b>Zapisz<i class=\"fa-solid fa-save\"></i></b> przy każdym polu, które chcesz zaktualizować.",
|
||||
"Device_MultiEdit_Fields": "Edytuj pola:",
|
||||
"Device_MultiEdit_MassActions": "Operacje zbiorcze:",
|
||||
"Device_MultiEdit_No_Devices": "",
|
||||
"Device_MultiEdit_Tooltip": "Uwaga. Kliknięcie tego spowoduje zastosowanie wartości po lewej stronie do wszystkich wybranych powyżej urządzeń.",
|
||||
"Device_Searchbox": "Szukaj",
|
||||
"Device_Shortcut_AllDevices": "Moje urządzenia",
|
||||
|
||||
@@ -199,6 +199,7 @@
|
||||
"Device_MultiEdit_Backup": "Cuidado, inserir valores errados abaixo interromperá sua configuração. Faça backup do seu banco de dados ou da configuração dos dispositivos primeiro (<a href=\"php/server/devices.php?action=ExportCSV\">clique para baixar <i class=\"fa-solid fa-download fa-bounce\"></i> </a>). Leia como recuperar dispositivos deste arquivo no <a href=\"https://github.com/jokob-sk/NetAlertX/blob/main/docs/BACKUPS.md#scenario-2-corrupted-database\" target=\" _blank\">Documentação de backups</a>.",
|
||||
"Device_MultiEdit_Fields": "Editar campos:",
|
||||
"Device_MultiEdit_MassActions": "Ações em massa:",
|
||||
"Device_MultiEdit_No_Devices": "",
|
||||
"Device_MultiEdit_Tooltip": "Cuidadoso. Clicar aqui aplicará o valor à esquerda a todos os dispositivos selecionados acima.",
|
||||
"Device_Searchbox": "Procurar",
|
||||
"Device_Shortcut_AllDevices": "Meus dispositivos",
|
||||
|
||||
@@ -199,6 +199,7 @@
|
||||
"Device_MultiEdit_Backup": "",
|
||||
"Device_MultiEdit_Fields": "Editar campos:",
|
||||
"Device_MultiEdit_MassActions": "Ações em massa:",
|
||||
"Device_MultiEdit_No_Devices": "",
|
||||
"Device_MultiEdit_Tooltip": "Cuidadoso. Clicar aqui aplicará o valor à esquerda a todos os dispositivos selecionados acima.",
|
||||
"Device_Searchbox": "Procurar",
|
||||
"Device_Shortcut_AllDevices": "",
|
||||
@@ -760,4 +761,4 @@
|
||||
"settings_system_label": "",
|
||||
"settings_update_item_warning": "",
|
||||
"test_event_tooltip": "Guarde as alterações antes de testar as definições."
|
||||
}
|
||||
}
|
||||
@@ -199,6 +199,7 @@
|
||||
"Device_MultiEdit_Backup": "Будьте осторожны: ввод неправильных значений ниже приведет к поломке вашей настройки. Сначала сделайте резервную копию базы данных или конфигурации устройств (<a href=\"php/server/devices.php?action=ExportCSV\">нажмите для загрузки <i class=\"fa-solid fa-download fa-bounce\"></i></a>). О том, как восстановить Устройства из этого файла, читайте в разделе <a href=\"https://github.com/jokob-sk/NetAlertX/blob/main/docs/BACKUPS.md#scenario-2-corrupted-database\" target=\"_blank\">Документация о резервном копировании</a>. Чтобы применить свои изменения, нажмите на значок <b> Сохранить <i class = \"fa-solid fa-save\"> </i> </b> в каждом поле, которое вы хотите обновить.",
|
||||
"Device_MultiEdit_Fields": "Редактировать поля:",
|
||||
"Device_MultiEdit_MassActions": "Массовые действия:",
|
||||
"Device_MultiEdit_No_Devices": "Устройства не выбраны.",
|
||||
"Device_MultiEdit_Tooltip": "Осторожно. При нажатии на эту кнопку значение слева будет применено ко всем устройствам, выбранным выше.",
|
||||
"Device_Searchbox": "Поиск",
|
||||
"Device_Shortcut_AllDevices": "Мои устройства",
|
||||
|
||||
@@ -199,6 +199,7 @@
|
||||
"Device_MultiEdit_Backup": "Dikkat, aşağıya yanlış değerler girmeniz yapılandırmanızı bozabilir. Lütfen önce veritabanınızı veya Cihazlar yapılandırmanızı yedekleyin (<a href=\"php/server/devices.php?action=ExportCSV\">İndirmeniz için tıklayın <i class=\"fa-solid fa-download fa-bounce\"></i></a>). Bu dosyadan Cihazları nasıl geri yükleyeceğinizi öğrenmek için <a href=\"https://github.com/jokob-sk/NetAlertX/blob/main/docs/BACKUPS.md#scenario-2-corrupted-database\" target=\"_blank\">Yedekleme dökümantasyonunu</a> okuyun.",
|
||||
"Device_MultiEdit_Fields": "Alanları Düzenle:",
|
||||
"Device_MultiEdit_MassActions": "Toplu komutlar:",
|
||||
"Device_MultiEdit_No_Devices": "",
|
||||
"Device_MultiEdit_Tooltip": "Dikkat. Buna tıklamak, soldaki değeri yukarıda seçilen tüm cihazlara uygulayacaktır.",
|
||||
"Device_Searchbox": "Arama",
|
||||
"Device_Shortcut_AllDevices": "Cihazlarım",
|
||||
|
||||
@@ -199,6 +199,7 @@
|
||||
"Device_MultiEdit_Backup": "Обережно, введення неправильних значень нижче призведе до порушення роботи налаштувань. Спочатку створіть резервну копію бази даних або конфігурації пристроїв (<a href=\"php/server/devices.php?action=ExportCSV\">натисніть, щоб завантажити <i class=\"fa-solid fa-download fa-bounce\"></i></a>). Прочитайте, як відновити пристрої з цього файлу, у <a href=\"https://github.com/jokob-sk/NetAlertX/blob/main/docs/BACKUPS.md#scenario-2-corrupted-database\" target=\"_blank\">документації щодо резервних копій</a>. Щоб застосувати зміни, натисніть значок <b>Зберегти<i class=\"fa-solid fa-save\"></i></b> у кожному полі, яке потрібно оновити.",
|
||||
"Device_MultiEdit_Fields": "Редагувати поля:",
|
||||
"Device_MultiEdit_MassActions": "Масові акції:",
|
||||
"Device_MultiEdit_No_Devices": "Не вибрано жодного пристрою.",
|
||||
"Device_MultiEdit_Tooltip": "Обережно. Якщо натиснути це, значення зліва буде застосовано до всіх пристроїв, вибраних вище.",
|
||||
"Device_Searchbox": "Пошук",
|
||||
"Device_Shortcut_AllDevices": "Мої пристрої",
|
||||
@@ -760,4 +761,4 @@
|
||||
"settings_system_label": "Система",
|
||||
"settings_update_item_warning": "Оновіть значення нижче. Слідкуйте за попереднім форматом. <b>Перевірка не виконана.</b>",
|
||||
"test_event_tooltip": "Перш ніж перевіряти налаштування, збережіть зміни."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -199,6 +199,7 @@
|
||||
"Device_MultiEdit_Backup": "小心,输入错误的值将破坏您的设置。请先备份您的数据库或设备配置(<a href=\"php/server/devices.php?action=ExportCSV\">点击下载<i class=\"fa-solid fa-download fa-bounce\"></i></a>)。在<a href=\"https://github.com/jokob-sk/NetAlertX/blob/main/docs/BACKUPS.md#scenario-2-corrupted-database\" target=\"_blank\">备份文档</a>中了解如何从此文件恢复设备。要应用更改,请在每个需要更新的字段点击<b>保存<i class='fa-solid fa-save'></i></b>图标。",
|
||||
"Device_MultiEdit_Fields": "编辑:",
|
||||
"Device_MultiEdit_MassActions": "谨慎操作:",
|
||||
"Device_MultiEdit_No_Devices": "未选择设备。",
|
||||
"Device_MultiEdit_Tooltip": "小心。 单击此按钮会将左侧的值应用到上面选择的所有设备。",
|
||||
"Device_Searchbox": "搜索",
|
||||
"Device_Shortcut_AllDevices": "我的设备",
|
||||
|
||||
@@ -1,36 +1,32 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
import json
|
||||
import subprocess
|
||||
import argparse
|
||||
import os
|
||||
import pathlib
|
||||
import sys
|
||||
from datetime import datetime
|
||||
import time
|
||||
import re
|
||||
import unicodedata
|
||||
import paho.mqtt.client as mqtt
|
||||
# from paho.mqtt import client as mqtt_client
|
||||
# from paho.mqtt import CallbackAPIVersion as mqtt_CallbackAPIVersion
|
||||
import hashlib
|
||||
import sqlite3
|
||||
from pytz import timezone
|
||||
|
||||
|
||||
# Register NetAlertX directories
|
||||
INSTALL_PATH="/app"
|
||||
INSTALL_PATH = "/app"
|
||||
sys.path.extend([f"{INSTALL_PATH}/front/plugins", f"{INSTALL_PATH}/server"])
|
||||
|
||||
# NetAlertX modules
|
||||
import conf
|
||||
from const import apiPath, confFileName, logPath
|
||||
from const import confFileName, logPath
|
||||
from plugin_utils import getPluginObject
|
||||
from plugin_helper import Plugin_Objects
|
||||
from logger import mylog, Logger, append_line_to_file
|
||||
from helper import timeNowTZ, get_setting_value, bytes_to_string, sanitize_string, normalize_string
|
||||
from models.notification_instance import NotificationInstance
|
||||
from logger import mylog, Logger
|
||||
from helper import timeNowTZ, get_setting_value, bytes_to_string, \
|
||||
sanitize_string, normalize_string
|
||||
from database import DB, get_device_stats
|
||||
from pytz import timezone
|
||||
|
||||
|
||||
# Make sure the TIMEZONE for logging is correct
|
||||
conf.tz = timezone(get_setting_value('TIMEZONE'))
|
||||
@@ -49,20 +45,19 @@ plugin_objects = Plugin_Objects(RESULT_FILE)
|
||||
md5_hash = hashlib.md5()
|
||||
|
||||
|
||||
|
||||
# globals
|
||||
mqtt_sensors = []
|
||||
mqtt_connected_to_broker = False
|
||||
mqtt_client = None # mqtt client
|
||||
topic_root = get_setting_value('MQTT_topic_root')
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
mylog('verbose', [f'[{pluginName}](publisher) In script'])
|
||||
|
||||
|
||||
mylog('verbose', [f'[{pluginName}](publisher) In script'])
|
||||
|
||||
# Check if basic config settings supplied
|
||||
if check_config() == False:
|
||||
mylog('verbose', [f'[{pluginName}] ⚠ ERROR: Publisher notification gateway not set up correctly. Check your {confFileName} {pluginName}_* variables.'])
|
||||
if not check_config():
|
||||
return
|
||||
|
||||
# Create a database connection
|
||||
@@ -70,60 +65,85 @@ def main():
|
||||
db.open()
|
||||
|
||||
mqtt_start(db)
|
||||
mqtt_client.disconnect()
|
||||
|
||||
plugin_objects.write_result_file()
|
||||
|
||||
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# -----------------------------------------------------------------------------
|
||||
# MQTT
|
||||
#-------------------------------------------------------------------------------
|
||||
#-------------------------------------------------------------------------------
|
||||
# -----------------------------------------------------------------------------
|
||||
# -----------------------------------------------------------------------------
|
||||
def check_config():
|
||||
if get_setting_value('MQTT_BROKER') == '' or get_setting_value('MQTT_PORT') == '' or get_setting_value('MQTT_USER') == '' or get_setting_value('MQTT_PASSWORD') == '':
|
||||
mylog('verbose', [f'[Check Config] ⚠ ERROR: MQTT service not set up correctly. Check your {confFileName} MQTT_* variables.'])
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
"""
|
||||
Checks whether the MQTT configuration settings are properly set.
|
||||
|
||||
Returns:
|
||||
bool: True if all required MQTT settings
|
||||
('MQTT_BROKER', 'MQTT_PORT', 'MQTT_USER', 'MQTT_PASSWORD')
|
||||
are non-empty;
|
||||
False otherwise. Logs a verbose error message
|
||||
if any setting is missing.
|
||||
"""
|
||||
if get_setting_value('MQTT_BROKER') == '' \
|
||||
or get_setting_value('MQTT_PORT') == '' \
|
||||
or get_setting_value('MQTT_USER') == '' \
|
||||
or get_setting_value('MQTT_PASSWORD') == '':
|
||||
mylog('verbose', [f'[Check Config] ⚠ ERROR: MQTT service not set up \
|
||||
correctly. Check your {confFileName} MQTT_* variables.'])
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# Sensor configs are tracking which sensors in NetAlertX exist and if a config has changed
|
||||
# -----------------------------------------------------------------------------
|
||||
# Sensor configs are tracking which sensors in NetAlertX exist
|
||||
# and if a config has changed
|
||||
class sensor_config:
|
||||
def __init__(self, deviceId, deviceName, sensorType, sensorName, icon, mac):
|
||||
def __init__(self,
|
||||
deviceId,
|
||||
deviceName,
|
||||
sensorType,
|
||||
sensorName,
|
||||
icon,
|
||||
mac):
|
||||
"""
|
||||
Initialize the sensor_config object with provided parameters. Sets up sensor configuration
|
||||
and generates necessary MQTT topics and messages based on the sensor type.
|
||||
Initialize the sensor_config object with provided parameters.
|
||||
Sets up sensor configuration and generates necessary MQTT topics
|
||||
and messages based on the sensor type.
|
||||
"""
|
||||
# Assign initial attributes
|
||||
self.deviceId = deviceId
|
||||
self.deviceName = deviceName
|
||||
self.sensorType = sensorType
|
||||
self.sensorName = sensorName
|
||||
self.icon = icon
|
||||
self.icon = icon
|
||||
self.mac = mac
|
||||
self.model = deviceName
|
||||
self.hash = ''
|
||||
self.model = deviceName
|
||||
self.hash = ''
|
||||
self.state_topic = ''
|
||||
self.json_attr_topic = ''
|
||||
self.topic = ''
|
||||
self.message = {} # Initialize message as an empty dictionary
|
||||
self.unique_id = ''
|
||||
|
||||
# Call helper functions to initialize the message, generate a hash, and handle plugin object
|
||||
# Call helper functions to initialize the message, generate a hash,
|
||||
# and handle plugin object
|
||||
self.initialize_message()
|
||||
self.generate_hash()
|
||||
self.handle_plugin_object()
|
||||
|
||||
def initialize_message(self):
|
||||
"""
|
||||
Initialize the MQTT message payload based on the sensor type. This method handles sensors of types:
|
||||
Initialize the MQTT message payload based on the sensor type.
|
||||
This method handles sensors of types:
|
||||
- 'timestamp'
|
||||
- 'binary_sensor'
|
||||
- 'sensor'
|
||||
- 'device_tracker'
|
||||
"""
|
||||
# Ensure self.message is initialized as a dictionary if not already done
|
||||
# Ensure self.message is initialized as a dictionary
|
||||
# if not already done
|
||||
if not isinstance(self.message, dict):
|
||||
self.message = {}
|
||||
|
||||
@@ -153,7 +173,6 @@ class sensor_config:
|
||||
"icon": f'mdi:{self.icon}'
|
||||
})
|
||||
|
||||
|
||||
# Handle 'device_tracker' sensor type
|
||||
elif self.sensorType == 'device_tracker':
|
||||
self.topic = f'homeassistant/device_tracker/{self.deviceId}/config'
|
||||
@@ -229,25 +248,36 @@ class sensor_config:
|
||||
)
|
||||
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# -------------------------------------------------------------------------------
|
||||
|
||||
def publish_mqtt(mqtt_client, topic, message):
|
||||
"""
|
||||
Publishes a message to an MQTT topic using the provided MQTT client.
|
||||
If the message is not a string, it is converted to a JSON-formatted string.
|
||||
The function retrieves the desired QoS level from settings and logs the publishing process.
|
||||
If the client is not connected to the broker, the function logs an error and aborts.
|
||||
It attempts to publish the message, retrying until the publish status indicates success.
|
||||
Args:
|
||||
mqtt_client: The MQTT client instance used to publish the message.
|
||||
topic (str): The MQTT topic to publish to.
|
||||
message (Any): The message payload to send. Non-string messages are converted to JSON.
|
||||
Returns:
|
||||
bool: True if the message was published successfully, False if not connected to the broker.
|
||||
"""
|
||||
status = 1
|
||||
|
||||
# convert anything but a simple string to json
|
||||
if not isinstance(message, str):
|
||||
message = json.dumps(message).replace("'",'"')
|
||||
message = json.dumps(message).replace("'", '"')
|
||||
|
||||
qos = get_setting_value('MQTT_QOS')
|
||||
|
||||
mylog('verbose', [f"[{pluginName}] Sending MQTT topic: {topic}"])
|
||||
mylog('verbose', [f"[{pluginName}] Sending MQTT message: {message}"])
|
||||
mylog('debug', [f"[{pluginName}] Sending MQTT topic: {topic}",
|
||||
f"[{pluginName}] Sending MQTT message: {message}"])
|
||||
# mylog('verbose', [f"[{pluginName}] get_setting_value('MQTT_QOS'): {qos}"])
|
||||
|
||||
if mqtt_connected_to_broker == False:
|
||||
|
||||
mylog('verbose', [f"[{pluginName}] ⚠ ERROR: Not connected to broker, aborting."])
|
||||
|
||||
if not mqtt_connected_to_broker:
|
||||
mylog('minimal', [f"[{pluginName}] ⚠ ERROR: Not connected to broker, aborting."])
|
||||
return False
|
||||
|
||||
while status != 0:
|
||||
@@ -267,45 +297,46 @@ def publish_mqtt(mqtt_client, topic, message):
|
||||
# mylog('verbose', [f"[{pluginName}] status: {status}"])
|
||||
# mylog('verbose', [f"[{pluginName}] result: {result}"])
|
||||
|
||||
if status != 0:
|
||||
mylog('verbose', [f"[{pluginName}] Waiting to reconnect to MQTT broker"])
|
||||
time.sleep(0.1)
|
||||
if status != 0:
|
||||
mylog('debug', [f"[{pluginName}] Waiting to reconnect to MQTT broker"])
|
||||
time.sleep(0.1)
|
||||
return True
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# ------------------------------------------------------------------------------
|
||||
# Create a generic device for overal stats
|
||||
def create_generic_device(mqtt_client, deviceId, deviceName):
|
||||
|
||||
create_sensor(mqtt_client, deviceId, deviceName, 'sensor', 'online', 'wifi-check')
|
||||
create_sensor(mqtt_client, deviceId, deviceName, 'sensor', 'down', 'wifi-cancel')
|
||||
def create_generic_device(mqtt_client, deviceId, deviceName):
|
||||
|
||||
create_sensor(mqtt_client, deviceId, deviceName, 'sensor', 'online', 'wifi-check')
|
||||
create_sensor(mqtt_client, deviceId, deviceName, 'sensor', 'down', 'wifi-cancel')
|
||||
create_sensor(mqtt_client, deviceId, deviceName, 'sensor', 'all', 'wifi')
|
||||
create_sensor(mqtt_client, deviceId, deviceName, 'sensor', 'archived', 'wifi-lock')
|
||||
create_sensor(mqtt_client, deviceId, deviceName, 'sensor', 'new', 'wifi-plus')
|
||||
create_sensor(mqtt_client, deviceId, deviceName, 'sensor', 'unknown', 'wifi-alert')
|
||||
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Register sensor config on the broker
|
||||
def create_sensor(mqtt_client, deviceId, deviceName, sensorType, sensorName, icon, mac=""):
|
||||
|
||||
global mqtt_sensors
|
||||
def create_sensor(mqtt_client, deviceId, deviceName, sensorType, sensorName, icon, mac=""):
|
||||
global mqtt_sensors
|
||||
|
||||
# check previous configs
|
||||
sensorConfig = sensor_config(deviceId, deviceName, sensorType, sensorName, icon, mac)
|
||||
sensorConfig = sensor_config(deviceId, deviceName, sensorType, sensorName, icon, mac)
|
||||
|
||||
# send if new
|
||||
if sensorConfig.isNew:
|
||||
# Create the HA sensor config if a new device is discovered
|
||||
if sensorConfig.isNew:
|
||||
|
||||
# add the sensor to the global list to keep track of succesfully added sensors
|
||||
if publish_mqtt(mqtt_client, sensorConfig.topic, sensorConfig.message):
|
||||
# hack - delay adding to the queue in case the process is
|
||||
time.sleep(get_setting_value('MQTT_DELAY_SEC')) # restarted and previous publish processes aborted
|
||||
# (it takes ~2s to update a sensor config on the broker)
|
||||
mqtt_sensors.append(sensorConfig)
|
||||
if publish_mqtt(mqtt_client, sensorConfig.topic, sensorConfig.message):
|
||||
# hack - delay adding to the queue in case the process is
|
||||
# restarted and previous publish processes aborted
|
||||
# (it takes ~2s to update a sensor config on the broker)
|
||||
time.sleep(get_setting_value('MQTT_DELAY_SEC'))
|
||||
mqtt_sensors.append(sensorConfig)
|
||||
|
||||
return sensorConfig
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
def mqtt_create_client():
|
||||
|
||||
# attempt reconnections on failure, ref https://www.emqx.com/en/blog/how-to-use-mqtt-in-python
|
||||
@@ -313,11 +344,11 @@ def mqtt_create_client():
|
||||
RECONNECT_RATE = 2
|
||||
MAX_RECONNECT_COUNT = 12
|
||||
MAX_RECONNECT_DELAY = 60
|
||||
|
||||
mytransport = 'tcp' # or 'websockets'
|
||||
|
||||
mytransport = 'tcp' # or 'websockets'
|
||||
|
||||
def on_disconnect(mqtt_client, userdata, rc):
|
||||
|
||||
|
||||
global mqtt_connected_to_broker
|
||||
|
||||
mylog('verbose', [f"[{pluginName}] Connection terminated, reason_code: {rc}"])
|
||||
@@ -328,7 +359,7 @@ def mqtt_create_client():
|
||||
|
||||
try:
|
||||
mqtt_client.reconnect()
|
||||
mqtt_connected_to_broker = True # Signal connection
|
||||
mqtt_connected_to_broker = True # Signal connection
|
||||
mylog('verbose', [f"[{pluginName}] Reconnected successfully"])
|
||||
return
|
||||
except Exception as err:
|
||||
@@ -338,19 +369,18 @@ def mqtt_create_client():
|
||||
reconnect_delay *= RECONNECT_RATE
|
||||
reconnect_delay = min(reconnect_delay, MAX_RECONNECT_DELAY)
|
||||
reconnect_count += 1
|
||||
|
||||
|
||||
mqtt_connected_to_broker = False
|
||||
|
||||
|
||||
def on_connect(mqtt_client, userdata, flags, rc, properties):
|
||||
|
||||
|
||||
global mqtt_connected_to_broker
|
||||
|
||||
# REF: Good docu on reason codes: https://www.emqx.com/en/blog/mqtt5-new-features-reason-code-and-ack
|
||||
if rc == 0:
|
||||
mylog('verbose', [f"[{pluginName}] Connected to broker"])
|
||||
mqtt_connected_to_broker = True # Signal connection
|
||||
else:
|
||||
if rc == 0:
|
||||
mylog('verbose', [f"[{pluginName}] Connected to broker"])
|
||||
mqtt_connected_to_broker = True # Signal connection
|
||||
else:
|
||||
mylog('verbose', [f"[{pluginName}] Connection failed, reason_code: {rc}"])
|
||||
mqtt_connected_to_broker = False
|
||||
|
||||
@@ -367,10 +397,12 @@ def mqtt_create_client():
|
||||
version = mqtt.MQTTv5
|
||||
|
||||
# we now hardcode the client id into here.
|
||||
# TODO: Add config ffor client id
|
||||
# TODO: Add config for client id (atm, we use a fixed client id,
|
||||
# so only one instance of NetAlertX can connect to the broker at any given time)
|
||||
# If you intend to run multiple instances simultaneously, make sure to set unique client IDs for each instance.
|
||||
mqtt_client = mqtt.Client(
|
||||
client_id='netalertx',
|
||||
callback_api_version = mqtt.CallbackAPIVersion.VERSION2,
|
||||
callback_api_version=mqtt.CallbackAPIVersion.VERSION2,
|
||||
transport=mytransport,
|
||||
protocol=version)
|
||||
mqtt_client.on_connect = on_connect
|
||||
@@ -379,8 +411,8 @@ def mqtt_create_client():
|
||||
if get_setting_value('MQTT_TLS'):
|
||||
mqtt_client.tls_set()
|
||||
|
||||
mqtt_client.username_pw_set(username = get_setting_value('MQTT_USER'), password = get_setting_value('MQTT_PASSWORD'))
|
||||
err_code = mqtt_client.connect(host = get_setting_value('MQTT_BROKER'), port = get_setting_value('MQTT_PORT'))
|
||||
mqtt_client.username_pw_set(username=get_setting_value('MQTT_USER'), password=get_setting_value('MQTT_PASSWORD'))
|
||||
err_code = mqtt_client.connect(host=get_setting_value('MQTT_BROKER'), port=get_setting_value('MQTT_PORT'))
|
||||
if (err_code == mqtt.MQTT_ERR_SUCCESS):
|
||||
# We (prematurely) set the connection state to connected
|
||||
# the callback may be delayed
|
||||
@@ -389,36 +421,36 @@ def mqtt_create_client():
|
||||
# Mosquitto works straight away
|
||||
# EMQX has a delay and does not update in loop below, so we cannot rely on it, we wait 1 sec
|
||||
time.sleep(1)
|
||||
mqtt_client.loop_start()
|
||||
mqtt_client.loop_start()
|
||||
|
||||
return mqtt_client
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
def mqtt_start(db):
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
def mqtt_start(db):
|
||||
|
||||
global mqtt_client, mqtt_connected_to_broker
|
||||
|
||||
if mqtt_connected_to_broker == False:
|
||||
mqtt_connected_to_broker = True
|
||||
mqtt_client = mqtt_create_client()
|
||||
if not mqtt_connected_to_broker:
|
||||
mqtt_client = mqtt_create_client()
|
||||
|
||||
|
||||
deviceName = get_setting_value('MQTT_DEVICE_NAME')
|
||||
deviceId = get_setting_value('MQTT_DEVICE_ID')
|
||||
|
||||
# General stats
|
||||
deviceId = get_setting_value('MQTT_DEVICE_ID')
|
||||
|
||||
# General stats
|
||||
|
||||
# Create a generic device for overal stats
|
||||
if get_setting_value('MQTT_SEND_STATS') == True:
|
||||
# Create a new device representing overall stats
|
||||
if get_setting_value('MQTT_SEND_STATS'):
|
||||
# Create a new device representing overall stats
|
||||
create_generic_device(mqtt_client, deviceId, deviceName)
|
||||
|
||||
# Get the data
|
||||
row = get_device_stats(db)
|
||||
row = get_device_stats(db)
|
||||
|
||||
# Publish (wrap into {} and remove last ',' from above)
|
||||
publish_mqtt(mqtt_client, f"{topic_root}/sensor/{deviceId}/state",
|
||||
{
|
||||
publish_mqtt(mqtt_client, f"{topic_root}/sensor/{deviceId}/state",
|
||||
{
|
||||
"online": row[0],
|
||||
"down": row[1],
|
||||
"all": row[2],
|
||||
@@ -429,7 +461,7 @@ def mqtt_start(db):
|
||||
)
|
||||
|
||||
# Generate device-specific MQTT messages if enabled
|
||||
if get_setting_value('MQTT_SEND_DEVICES') == True:
|
||||
if get_setting_value('MQTT_SEND_DEVICES'):
|
||||
|
||||
# Specific devices processing
|
||||
|
||||
@@ -438,37 +470,35 @@ def mqtt_start(db):
|
||||
|
||||
sec_delay = len(devices) * int(get_setting_value('MQTT_DELAY_SEC'))*5
|
||||
|
||||
mylog('verbose', [f"[{pluginName}] Estimated delay: ", (sec_delay), 's ', '(', round(sec_delay/60,1) , 'min)' ])
|
||||
mylog('verbose', [f"[{pluginName}] Estimated delay: ", (sec_delay), 's ', '(', round(sec_delay/60, 1), 'min)'])
|
||||
|
||||
debug_index = 0
|
||||
|
||||
for device in devices:
|
||||
for device in devices:
|
||||
|
||||
# # debug statement START 🔻
|
||||
# if 'Moto' not in device["devName"]:
|
||||
# mylog('none', [f"[{pluginName}] ALERT - ⚠⚠⚠⚠ DEBUGGING ⚠⚠⚠⚠ - this should not be in uncommented in production"])
|
||||
# mylog('none', [f"[{pluginName}] ALERT - ⚠⚠⚠⚠ DEBUGGING ⚠⚠⚠⚠ - this should not be in uncommented in production"])
|
||||
# continue
|
||||
# # debug statement END 🔺
|
||||
|
||||
|
||||
# Create devices in Home Assistant - send config messages
|
||||
deviceId = 'mac_' + device["devMac"].replace(" ", "").replace(":", "_").lower()
|
||||
# Normalize the string and remove unwanted characters
|
||||
devDisplayName = re.sub('[^a-zA-Z0-9-_\\s]', '', normalize_string(device["devName"]))
|
||||
devDisplayName = re.sub('[^a-zA-Z0-9-_\\s]', '', normalize_string(device["devName"]))
|
||||
|
||||
sensorConfig = create_sensor(mqtt_client, deviceId, devDisplayName, 'sensor', 'last_ip', 'ip-network', device["devMac"])
|
||||
sensorConfig = create_sensor(mqtt_client, deviceId, devDisplayName, 'sensor', 'mac_address', 'folder-key-network', device["devMac"])
|
||||
sensorConfig = create_sensor(mqtt_client, deviceId, devDisplayName, 'sensor', 'is_new', 'bell-alert-outline', device["devMac"])
|
||||
sensorConfig = create_sensor(mqtt_client, deviceId, devDisplayName, 'sensor', 'vendor', 'cog', device["devMac"])
|
||||
sensorConfig = create_sensor(mqtt_client, deviceId, devDisplayName, 'sensor', 'vendor', 'cog', device["devMac"])
|
||||
sensorConfig = create_sensor(mqtt_client, deviceId, devDisplayName, 'sensor', 'first_connection', 'calendar-start', device["devMac"])
|
||||
sensorConfig = create_sensor(mqtt_client, deviceId, devDisplayName, 'sensor', 'last_connection', 'calendar-end', device["devMac"])
|
||||
|
||||
|
||||
# handle device_tracker
|
||||
# IMPORTANT: shared payload - device_tracker attributes and individual sensors
|
||||
devJson = {
|
||||
"last_ip": device["devLastIP"],
|
||||
"is_new": str(device["devIsNew"]),
|
||||
"alert_down": str(device["devAlertDown"]),
|
||||
"vendor": sanitize_string(device["devVendor"]),
|
||||
devJson = {
|
||||
"last_ip": device["devLastIP"],
|
||||
"is_new": str(device["devIsNew"]),
|
||||
"alert_down": str(device["devAlertDown"]),
|
||||
"vendor": sanitize_string(device["devVendor"]),
|
||||
"mac_address": str(device["devMac"]),
|
||||
"model": devDisplayName,
|
||||
"last_connection": prepTimeStamp(str(device["devLastConnection"])),
|
||||
@@ -480,53 +510,48 @@ def mqtt_start(db):
|
||||
"network_parent_name": next((dev["devName"] for dev in devices if dev["devMAC"] == device["devParentMAC"]), "")
|
||||
}
|
||||
|
||||
# bulk update device sensors in home assistant
|
||||
# bulk update device sensors in home assistant
|
||||
publish_mqtt(mqtt_client, sensorConfig.state_topic, devJson) # REQUIRED, DON'T DELETE
|
||||
|
||||
|
||||
# create and update is_present sensor
|
||||
sensorConfig = create_sensor(mqtt_client, deviceId, devDisplayName, 'binary_sensor', 'is_present', 'wifi', device["devMac"])
|
||||
publish_mqtt(mqtt_client, sensorConfig.state_topic,
|
||||
{
|
||||
publish_mqtt(mqtt_client, sensorConfig.state_topic,
|
||||
{
|
||||
"is_present": to_binary_sensor(str(device["devPresentLastScan"]))
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
# handle device_tracker
|
||||
sensorConfig = create_sensor(mqtt_client, deviceId, devDisplayName, 'device_tracker', 'is_home', 'home', device["devMac"])
|
||||
sensorConfig = create_sensor(mqtt_client, deviceId, devDisplayName, 'device_tracker', 'is_home', 'home', device["devMac"])
|
||||
|
||||
# <away|home> are only valid states
|
||||
state = 'away'
|
||||
if to_binary_sensor(str(device["devPresentLastScan"])) == "ON":
|
||||
state = 'home'
|
||||
|
||||
publish_mqtt(mqtt_client, sensorConfig.state_topic, state)
|
||||
|
||||
publish_mqtt(mqtt_client, sensorConfig.state_topic, state)
|
||||
|
||||
# publish device_tracker attributes
|
||||
publish_mqtt(mqtt_client, sensorConfig.json_attr_topic, devJson)
|
||||
publish_mqtt(mqtt_client, sensorConfig.json_attr_topic, devJson)
|
||||
|
||||
|
||||
|
||||
#===============================================================================
|
||||
# =============================================================================
|
||||
# Home Assistant UTILs
|
||||
#===============================================================================
|
||||
# =============================================================================
|
||||
def to_binary_sensor(input):
|
||||
# In HA a binary sensor returns ON or OFF
|
||||
result = "OFF"
|
||||
"""
|
||||
Converts various input types to a binary sensor state ("ON" or "OFF") for Home Assistant.
|
||||
"""
|
||||
if isinstance(input, (int, float)) and input >= 1:
|
||||
return "ON"
|
||||
elif isinstance(input, bool) and input:
|
||||
return "ON"
|
||||
elif isinstance(input, str) and input == "1":
|
||||
return "ON"
|
||||
elif isinstance(input, bytes) and bytes_to_string(input) == "1":
|
||||
return "ON"
|
||||
return "OFF"
|
||||
|
||||
# bytestring
|
||||
if isinstance(input, str):
|
||||
if input == "1":
|
||||
result = "ON"
|
||||
elif isinstance(input, int):
|
||||
if input == 1:
|
||||
result = "ON"
|
||||
elif isinstance(input, bool):
|
||||
if input == True:
|
||||
result = "ON"
|
||||
elif isinstance(input, bytes):
|
||||
if bytes_to_string(input) == "1":
|
||||
result = "ON"
|
||||
return result
|
||||
|
||||
# -------------------------------------
|
||||
# Convert to format that is interpretable by Home Assistant
|
||||
@@ -537,7 +562,7 @@ def prepTimeStamp(datetime_str):
|
||||
|
||||
# If the parsed datetime is naive (i.e., does not contain timezone info), add UTC timezone
|
||||
if parsed_datetime.tzinfo is None:
|
||||
parsed_datetime = parsed_datetime.replace(tzinfo=conf.tz)
|
||||
parsed_datetime = conf.tz.localize(parsed_datetime)
|
||||
|
||||
except ValueError:
|
||||
mylog('verbose', [f"[{pluginName}] Timestamp conversion failed of string '{datetime_str}'"])
|
||||
@@ -547,9 +572,7 @@ def prepTimeStamp(datetime_str):
|
||||
# Convert to the required format with 'T' between date and time and ensure the timezone is included
|
||||
return parsed_datetime.isoformat() # This will include the timezone offset
|
||||
|
||||
|
||||
# -------------INIT---------------------
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -64,7 +64,8 @@
|
||||
"name": "subnets",
|
||||
"type": "setting",
|
||||
"value": "SCAN_SUBNETS",
|
||||
"base64": true
|
||||
"base64": true,
|
||||
"timeoutMultiplier": true
|
||||
}
|
||||
],
|
||||
"settings": [
|
||||
@@ -387,6 +388,34 @@
|
||||
"string": "Arguments to run arps-scan with. Recommended and tested only with the setting: <br/> <code>sudo arp-scan --ignoredups --retry=6</code>."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"function": "DURATION",
|
||||
"type": {
|
||||
"dataType": "integer",
|
||||
"elements": [
|
||||
{
|
||||
"elementType": "input",
|
||||
"elementOptions": [{ "type": "number" }],
|
||||
"transformers": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"default_value": 0,
|
||||
"options": [],
|
||||
"localized": ["name", "description"],
|
||||
"name": [
|
||||
{
|
||||
"language_code": "en_us",
|
||||
"string": "Discovery duration"
|
||||
}
|
||||
],
|
||||
"description": [
|
||||
{
|
||||
"language_code": "en_us",
|
||||
"string": "If <code>DURATION</code> is not <code>0</code>, the scan runs repeatedly per interface for that many seconds. <strong>Important:</strong> <code>RUN_TIMEOUT</code> must be greater than <code>DURATION</code>, otherwise the scan will fail."
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"database_column_definitions": [
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
import os
|
||||
import time
|
||||
import pathlib
|
||||
import argparse
|
||||
import sys
|
||||
@@ -46,7 +47,7 @@ def main():
|
||||
plugin_objects = Plugin_Objects(RESULT_FILE)
|
||||
|
||||
# Print a message to indicate that the script is starting.
|
||||
mylog('verbose', ['[ARP Scan] In script '])
|
||||
mylog('verbose', [f'[{pluginName}] In script '])
|
||||
|
||||
# holds a list of user-submitted subnets.
|
||||
# mylog('verbose', ['[ARP Scan] values.userSubnets: ', values.userSubnets])
|
||||
@@ -150,16 +151,28 @@ def execute_arpscan_on_interface(interface):
|
||||
# Prepare command arguments
|
||||
arpscan_args = get_setting_value('ARPSCAN_ARGS').split() + interface.split()
|
||||
|
||||
# Execute command
|
||||
# Optional duration in seconds (0 = run once)
|
||||
try:
|
||||
# try running a subprocess safely
|
||||
result = subprocess.check_output(arpscan_args, universal_newlines=True)
|
||||
except subprocess.CalledProcessError as e:
|
||||
# An error occurred, handle it
|
||||
error_type = type(e).__name__ # Capture the error type
|
||||
result = ""
|
||||
scan_duration = int(get_setting_value('ARPSCAN_DURATION'))
|
||||
except Exception:
|
||||
scan_duration = 0 # default: single run
|
||||
|
||||
return result
|
||||
results = []
|
||||
start_time = time.time()
|
||||
|
||||
while True:
|
||||
try:
|
||||
result = subprocess.check_output(arpscan_args, universal_newlines=True)
|
||||
results.append(result)
|
||||
except subprocess.CalledProcessError as e:
|
||||
result = ""
|
||||
# stop looping if duration not set or expired
|
||||
if scan_duration == 0 or (time.time() - start_time) > scan_duration:
|
||||
break
|
||||
time.sleep(2) # short delay between scans
|
||||
|
||||
# concatenate all outputs (for regex parsing)
|
||||
return "\n".join(results)
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,232 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
import os
|
||||
import pathlib
|
||||
import sys
|
||||
import json
|
||||
|
||||
import subprocess
|
||||
|
||||
# Define the installation path and extend the system path for plugin imports
|
||||
INSTALL_PATH = "/app"
|
||||
sys.path.extend([f"{INSTALL_PATH}/front/plugins", f"{INSTALL_PATH}/server"])
|
||||
|
||||
from plugin_helper import Plugin_Object, Plugin_Objects, decodeBase64
|
||||
from plugin_utils import get_plugins_configs
|
||||
from logger import mylog, Logger
|
||||
from const import pluginsPath, fullDbPath, logPath
|
||||
from helper import timeNowTZ, get_setting_value
|
||||
from messaging.in_app import write_notification
|
||||
from database import DB
|
||||
from models.device_instance import DeviceInstance
|
||||
import conf
|
||||
from pytz import timezone
|
||||
|
||||
# Make sure the TIMEZONE for logging is correct
|
||||
conf.tz = timezone(get_setting_value('TIMEZONE'))
|
||||
|
||||
# Make sure log level is initialized correctly
|
||||
Logger(get_setting_value('LOG_LEVEL'))
|
||||
|
||||
pluginName = 'AVAHISCAN'
|
||||
|
||||
# Define the current path and log file paths
|
||||
LOG_PATH = logPath + '/plugins'
|
||||
LOG_FILE = os.path.join(LOG_PATH, f'script.{pluginName}.log')
|
||||
RESULT_FILE = os.path.join(LOG_PATH, f'last_result.{pluginName}.log')
|
||||
|
||||
# Initialize the Plugin obj output file
|
||||
plugin_objects = Plugin_Objects(RESULT_FILE)
|
||||
|
||||
|
||||
|
||||
def main():
|
||||
mylog('verbose', [f'[{pluginName}] In script'])
|
||||
|
||||
# Retrieve timeout from settings (use AVAHISCAN_RUN_TIMEOUT), fall back to 20
|
||||
try:
|
||||
_timeout_val = get_setting_value('AVAHISCAN_RUN_TIMEOUT')
|
||||
if _timeout_val is None or _timeout_val == '':
|
||||
timeout = 20
|
||||
else:
|
||||
try:
|
||||
timeout = int(_timeout_val)
|
||||
except (ValueError, TypeError):
|
||||
timeout = 20
|
||||
except Exception:
|
||||
timeout = 20
|
||||
|
||||
# Create a database connection
|
||||
db = DB() # instance of class DB
|
||||
db.open()
|
||||
|
||||
# Initialize the Plugin obj output file
|
||||
plugin_objects = Plugin_Objects(RESULT_FILE)
|
||||
|
||||
# Create a DeviceInstance instance
|
||||
device_handler = DeviceInstance(db)
|
||||
|
||||
# Retrieve devices
|
||||
if get_setting_value("REFRESH_FQDN"):
|
||||
devices = device_handler.getAll()
|
||||
else:
|
||||
devices = device_handler.getUnknown()
|
||||
|
||||
mylog('verbose', [f'[{pluginName}] Devices count: {len(devices)}'])
|
||||
|
||||
# Mock list of devices (replace with actual device_handler.getUnknown() in production)
|
||||
# devices = [
|
||||
# {'devMac': '00:11:22:33:44:55', 'devLastIP': '192.168.1.121'},
|
||||
# {'devMac': '00:11:22:33:44:56', 'devLastIP': '192.168.1.9'},
|
||||
# {'devMac': '00:11:22:33:44:57', 'devLastIP': '192.168.1.82'},
|
||||
# ]
|
||||
|
||||
if len(devices) > 0:
|
||||
# ensure service is running
|
||||
ensure_avahi_running()
|
||||
|
||||
for device in devices:
|
||||
domain_name = execute_name_lookup(device['devLastIP'], timeout)
|
||||
|
||||
# check if found and not a timeout ('to')
|
||||
if domain_name != '' and domain_name != 'to':
|
||||
plugin_objects.add_object(
|
||||
# "MAC", "IP", "Server", "Name"
|
||||
primaryId = device['devMac'],
|
||||
secondaryId = device['devLastIP'],
|
||||
watched1 = '', # You can add any relevant info here if needed
|
||||
watched2 = domain_name,
|
||||
watched3 = '',
|
||||
watched4 = '',
|
||||
extra = '',
|
||||
foreignKey = device['devMac'])
|
||||
|
||||
plugin_objects.write_result_file()
|
||||
|
||||
mylog('verbose', [f'[{pluginName}] Script finished'])
|
||||
|
||||
return 0
|
||||
|
||||
#===============================================================================
|
||||
# Execute scan
|
||||
#===============================================================================
|
||||
def execute_name_lookup(ip, timeout):
|
||||
"""
|
||||
Execute the avahi-resolve command on the IP.
|
||||
"""
|
||||
|
||||
args = ['avahi-resolve', '-a', ip]
|
||||
|
||||
# Execute command
|
||||
output = ""
|
||||
|
||||
try:
|
||||
mylog('debug', [f'[{pluginName}] DEBUG CMD :', args])
|
||||
|
||||
# Run the subprocess with a forced timeout
|
||||
output = subprocess.check_output(args, universal_newlines=True, stderr=subprocess.STDOUT, timeout=timeout)
|
||||
|
||||
mylog('debug', [f'[{pluginName}] DEBUG OUTPUT : {output}'])
|
||||
|
||||
domain_name = ''
|
||||
|
||||
# Split the output into lines
|
||||
lines = output.splitlines()
|
||||
|
||||
# Look for the resolved IP address
|
||||
for line in lines:
|
||||
if ip in line:
|
||||
parts = line.split()
|
||||
if len(parts) > 1:
|
||||
domain_name = parts[1] # Second part is the resolved domain name
|
||||
else:
|
||||
mylog('verbose', [f'[{pluginName}] ⚠ ERROR - Unexpected output format: {line}'])
|
||||
|
||||
mylog('debug', [f'[{pluginName}] Domain Name: {domain_name}'])
|
||||
|
||||
return domain_name
|
||||
|
||||
except subprocess.CalledProcessError as e:
|
||||
mylog('none', [f'[{pluginName}] ⚠ ERROR - {e.output}'])
|
||||
|
||||
except subprocess.TimeoutExpired as e:
|
||||
# Return a distinct value that main() checks for when a timeout occurs
|
||||
# Keep logging for telemetry/debugging
|
||||
mylog('none', [f'[{pluginName}] TIMEOUT - the process forcefully terminated as timeout reached{": " + str(getattr(e, "output", "")) if getattr(e, "output", None) else ""}'])
|
||||
return 'to'
|
||||
|
||||
if output == "":
|
||||
mylog('none', [f'[{pluginName}] Scan: FAIL - check logs'])
|
||||
else:
|
||||
mylog('debug', [f'[{pluginName}] Scan: SUCCESS'])
|
||||
|
||||
return ''
|
||||
|
||||
# Function to ensure Avahi and its dependencies are running
|
||||
def ensure_avahi_running(attempt=1, max_retries=2):
|
||||
"""
|
||||
Ensure that D-Bus is running and the Avahi daemon is started, with recursive retry logic.
|
||||
"""
|
||||
mylog('debug', [f'[{pluginName}] Attempt {attempt} - Ensuring D-Bus and Avahi daemon are running...'])
|
||||
|
||||
# Check rc-status
|
||||
try:
|
||||
subprocess.run(['rc-status'], check=True)
|
||||
except subprocess.CalledProcessError as e:
|
||||
mylog('none', [f'[{pluginName}] ⚠ ERROR - Failed to check rc-status: {e.output}'])
|
||||
return
|
||||
|
||||
# Create OpenRC soft level (wrap in try/except to keep error handling consistent)
|
||||
try:
|
||||
subprocess.run(['touch', '/run/openrc/softlevel'], check=True, capture_output=True, text=True)
|
||||
except subprocess.CalledProcessError as e:
|
||||
mylog('none', [f'[{pluginName}] ⚠ ERROR - Failed to create OpenRC soft level: {e.stderr if e.stderr else str(e)}'])
|
||||
return
|
||||
|
||||
# Add Avahi daemon to runlevel
|
||||
try:
|
||||
subprocess.run(['rc-update', 'add', 'avahi-daemon'], check=True)
|
||||
except subprocess.CalledProcessError as e:
|
||||
mylog('none', [f'[{pluginName}] ⚠ ERROR - Failed to add Avahi to runlevel: {e.output}'])
|
||||
return
|
||||
|
||||
# Start the D-Bus service
|
||||
try:
|
||||
subprocess.run(['rc-service', 'dbus', 'start'], check=True)
|
||||
except subprocess.CalledProcessError as e:
|
||||
mylog('none', [f'[{pluginName}] ⚠ ERROR - Failed to start D-Bus: {e.output}'])
|
||||
return
|
||||
|
||||
# Check Avahi status
|
||||
status_output = subprocess.run(['rc-service', 'avahi-daemon', 'status'], capture_output=True, text=True)
|
||||
if 'started' in status_output.stdout:
|
||||
mylog('debug', [f'[{pluginName}] Avahi Daemon is already running.'])
|
||||
return
|
||||
|
||||
mylog('none', [f'[{pluginName}] Avahi Daemon is not running, attempting to start... (Attempt {attempt})'])
|
||||
|
||||
# Start the Avahi daemon
|
||||
try:
|
||||
subprocess.run(['rc-service', 'avahi-daemon', 'start'], check=True)
|
||||
except subprocess.CalledProcessError as e:
|
||||
mylog('none', [f'[{pluginName}] ⚠ ERROR - Failed to start Avahi daemon: {e.output}'])
|
||||
|
||||
# Check status after starting
|
||||
status_output = subprocess.run(['rc-service', 'avahi-daemon', 'status'], capture_output=True, text=True)
|
||||
if 'started' in status_output.stdout:
|
||||
mylog('debug', [f'[{pluginName}] Avahi Daemon successfully started.'])
|
||||
return
|
||||
|
||||
# Retry if not started and attempts are left
|
||||
if attempt < max_retries:
|
||||
mylog('debug', [f'[{pluginName}] Retrying... ({attempt + 1}/{max_retries})'])
|
||||
ensure_avahi_running(attempt + 1, max_retries)
|
||||
else:
|
||||
mylog('none', [f'[{pluginName}] ⚠ ERROR - Avahi Daemon failed to start after {max_retries} attempts.'])
|
||||
|
||||
# rc-update add avahi-daemon
|
||||
# rc-service avahi-daemon status
|
||||
# rc-service avahi-daemon start
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -1,127 +1,142 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
#!/usr/bin/env python3
|
||||
import os
|
||||
import pathlib
|
||||
import sys
|
||||
import json
|
||||
import dns.resolver
|
||||
import socket
|
||||
import ipaddress
|
||||
from zeroconf import Zeroconf, ServiceBrowser, ServiceInfo, InterfaceChoice, IPVersion
|
||||
from zeroconf.asyncio import AsyncZeroconf
|
||||
|
||||
# Define the installation path and extend the system path for plugin imports
|
||||
INSTALL_PATH = "/app"
|
||||
sys.path.extend([f"{INSTALL_PATH}/front/plugins", f"{INSTALL_PATH}/server"])
|
||||
|
||||
from plugin_helper import Plugin_Object, Plugin_Objects, decodeBase64
|
||||
from plugin_utils import get_plugins_configs
|
||||
from logger import mylog as write_log, Logger
|
||||
from const import pluginsPath, fullDbPath, logPath
|
||||
from helper import timeNowTZ, get_setting_value
|
||||
from messaging.in_app import write_notification
|
||||
from plugin_helper import Plugin_Objects
|
||||
from logger import mylog, Logger
|
||||
from const import logPath
|
||||
from helper import get_setting_value
|
||||
from database import DB
|
||||
from models.device_instance import DeviceInstance
|
||||
import conf
|
||||
from pytz import timezone
|
||||
|
||||
# Make sure the TIMEZONE for logging is correct
|
||||
conf.tz = timezone(get_setting_value('TIMEZONE'))
|
||||
# Configure timezone and logging
|
||||
conf.tz = timezone(get_setting_value("TIMEZONE"))
|
||||
Logger(get_setting_value("LOG_LEVEL"))
|
||||
|
||||
# Make sure log level is initialized correctly
|
||||
Logger(get_setting_value('LOG_LEVEL'))
|
||||
pluginName = "AVAHISCAN"
|
||||
|
||||
pluginName = 'AVAHISCAN'
|
||||
# Define log paths
|
||||
LOG_PATH = os.path.join(logPath, "plugins")
|
||||
LOG_FILE = os.path.join(LOG_PATH, f"script.{pluginName}.log")
|
||||
RESULT_FILE = os.path.join(LOG_PATH, f"last_result.{pluginName}.log")
|
||||
|
||||
# Define the current path and log file paths
|
||||
LOG_PATH = logPath + '/plugins'
|
||||
LOG_FILE = os.path.join(LOG_PATH, f'script.{pluginName}.log')
|
||||
RESULT_FILE = os.path.join(LOG_PATH, f'last_result.{pluginName}.log')
|
||||
|
||||
# Initialize the Plugin obj output file
|
||||
# Initialize plugin results
|
||||
plugin_objects = Plugin_Objects(RESULT_FILE)
|
||||
|
||||
#===============================================================================
|
||||
# Execute scan using DNS resolver
|
||||
#===============================================================================
|
||||
def resolve_ips_with_zeroconf(ips, timeout):
|
||||
|
||||
# =============================================================================
|
||||
# Helper functions
|
||||
# =============================================================================
|
||||
|
||||
def resolve_mdns_name(ip: str, timeout: int = 5) -> str:
|
||||
"""
|
||||
Uses DNS resolver to actively query PTR records for reverse DNS lookups on given IP addresses.
|
||||
Attempts to resolve a hostname via multicast DNS using the Zeroconf library.
|
||||
|
||||
Args:
|
||||
ip (str): The IP address to resolve.
|
||||
timeout (int): Timeout in seconds for mDNS resolution.
|
||||
|
||||
Returns:
|
||||
str: Resolved hostname (or empty string if not found).
|
||||
"""
|
||||
resolved_hosts = {}
|
||||
|
||||
for ip in ips:
|
||||
mylog("debug", [f"[{pluginName}] Resolving mDNS for {ip}"])
|
||||
|
||||
# Convert string IP to an address object
|
||||
try:
|
||||
addr = ipaddress.ip_address(ip)
|
||||
except ValueError:
|
||||
mylog("none", [f"[{pluginName}] Invalid IP: {ip}"])
|
||||
return ""
|
||||
|
||||
# Reverse lookup name, e.g. "121.1.168.192.in-addr.arpa"
|
||||
if addr.version == 4:
|
||||
rev_name = ipaddress.ip_address(ip).reverse_pointer
|
||||
else:
|
||||
rev_name = ipaddress.ip_address(ip).reverse_pointer
|
||||
|
||||
try:
|
||||
zeroconf = Zeroconf()
|
||||
hostname = socket.getnameinfo((ip, 0), socket.NI_NAMEREQD)[0]
|
||||
zeroconf.close()
|
||||
if hostname and hostname != ip:
|
||||
mylog("debug", [f"[{pluginName}] Found mDNS name: {hostname}"])
|
||||
return hostname
|
||||
except Exception as e:
|
||||
mylog("debug", [f"[{pluginName}] Zeroconf lookup failed for {ip}: {e}"])
|
||||
finally:
|
||||
try:
|
||||
# Construct the reverse IP for PTR query (e.g., 8.1.168.192.in-addr.arpa.)
|
||||
reverse_ip = '.'.join(reversed(ip.split('.'))) + '.in-addr.arpa.'
|
||||
|
||||
# Query PTR record with timeout; respect the passed timeout per query
|
||||
answers = dns.resolver.resolve(reverse_ip, 'PTR', lifetime=max(1, timeout))
|
||||
|
||||
if answers:
|
||||
# For PTR records, the hostname is in the target field
|
||||
hostname = str(answers[0].target).rstrip('.')
|
||||
resolved_hosts[ip] = hostname
|
||||
write_log('verbose', [f'[{pluginName}] Resolved {ip} -> {hostname}'])
|
||||
except Exception as e:
|
||||
write_log('verbose', [f'[{pluginName}] Error resolving {ip}: {e}'])
|
||||
|
||||
write_log('verbose', [f'[{pluginName}] Active resolution finished. Found {len(resolved_hosts)} hosts.'])
|
||||
return resolved_hosts
|
||||
zeroconf.close()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return ""
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Main logic
|
||||
# =============================================================================
|
||||
|
||||
def main():
|
||||
write_log('verbose', [f'[{pluginName}] In script'])
|
||||
mylog("verbose", [f"[{pluginName}] Script started"])
|
||||
|
||||
# Get timeout from settings, default to 20s, and subtract a buffer
|
||||
try:
|
||||
timeout_setting = int(get_setting_value('AVAHISCAN_RUN_TIMEOUT'))
|
||||
except (ValueError, TypeError):
|
||||
timeout_setting = 30 # Default to 30s as a safe value
|
||||
timeout = get_setting_value("AVAHISCAN_RUN_TIMEOUT")
|
||||
use_mock = "--mockdata" in sys.argv
|
||||
|
||||
# Use a timeout 5 seconds less than the plugin's configured timeout to allow for cleanup
|
||||
scan_duration = max(5, timeout_setting - 5)
|
||||
|
||||
db = DB()
|
||||
db.open()
|
||||
|
||||
plugin_objects = Plugin_Objects(RESULT_FILE)
|
||||
device_handler = DeviceInstance(db)
|
||||
|
||||
# Retrieve devices based on REFRESH_FQDN setting to match original script's logic
|
||||
if get_setting_value("REFRESH_FQDN"):
|
||||
devices = device_handler.getAll()
|
||||
write_log('verbose', [f'[{pluginName}] REFRESH_FQDN is true, getting all devices.'])
|
||||
if use_mock:
|
||||
mylog("verbose", [f"[{pluginName}] Running in MOCK mode"])
|
||||
devices = [
|
||||
{"devMac": "00:11:22:33:44:55", "devLastIP": "192.168.1.121"},
|
||||
{"devMac": "00:11:22:33:44:56", "devLastIP": "192.168.1.9"},
|
||||
{"devMac": "00:11:22:33:44:57", "devLastIP": "192.168.1.82"},
|
||||
]
|
||||
else:
|
||||
devices = device_handler.getUnknown()
|
||||
write_log('verbose', [f'[{pluginName}] REFRESH_FQDN is false, getting devices with unknown hostnames.'])
|
||||
db = DB()
|
||||
db.open()
|
||||
device_handler = DeviceInstance(db)
|
||||
devices = (
|
||||
device_handler.getAll()
|
||||
if get_setting_value("REFRESH_FQDN")
|
||||
else device_handler.getUnknown()
|
||||
)
|
||||
|
||||
# db.close() # This was causing the crash, DB object doesn't have a close method.
|
||||
mylog("verbose", [f"[{pluginName}] Devices count: {len(devices)}"])
|
||||
|
||||
write_log('verbose', [f'[{pluginName}] Devices to scan: {len(devices)}'])
|
||||
for device in devices:
|
||||
ip = device["devLastIP"]
|
||||
mac = device["devMac"]
|
||||
|
||||
if len(devices) > 0:
|
||||
ips_to_find = [device['devLastIP'] for device in devices if device['devLastIP']]
|
||||
if ips_to_find:
|
||||
write_log('verbose', [f'[{pluginName}] IPs to be scanned: {ips_to_find}'])
|
||||
resolved_hosts = resolve_ips_with_zeroconf(ips_to_find, scan_duration)
|
||||
hostname = resolve_mdns_name(ip, timeout)
|
||||
|
||||
for device in devices:
|
||||
domain_name = resolved_hosts.get(device['devLastIP'])
|
||||
if domain_name:
|
||||
plugin_objects.add_object(
|
||||
primaryId = device['devMac'],
|
||||
secondaryId = device['devLastIP'],
|
||||
watched1 = '',
|
||||
watched2 = domain_name,
|
||||
watched3 = '',
|
||||
watched4 = '',
|
||||
extra = '',
|
||||
foreignKey = device['devMac']
|
||||
)
|
||||
else:
|
||||
write_log('verbose', [f'[{pluginName}] No devices with IP addresses to scan.'])
|
||||
if hostname:
|
||||
plugin_objects.add_object(
|
||||
primaryId=mac,
|
||||
secondaryId=ip,
|
||||
watched1="",
|
||||
watched2=hostname,
|
||||
watched3="",
|
||||
watched4="",
|
||||
extra="",
|
||||
foreignKey=mac,
|
||||
)
|
||||
|
||||
plugin_objects.write_result_file()
|
||||
|
||||
write_log('verbose', [f'[{pluginName}] Script finished'])
|
||||
|
||||
|
||||
mylog("verbose", [f"[{pluginName}] Script finished"])
|
||||
return 0
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
# =============================================================================
|
||||
# Entrypoint
|
||||
# =============================================================================
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
Plugin for pinging existing devices via the [ping](https://linux.die.net/man/8/ping) network utility. The devices have to be accessible from the container. You can use this plugin with other suplementing plugins as described in the [subnets docs](https://github.com/jokob-sk/NetAlertX/blob/main/docs/SUBNETS.md).
|
||||
|
||||
This plugin can be used if you are getting false offline positives on specific devices. See the [Fix offline detection guide](https://github.com/jokob-sk/NetAlertX/blob/main/docs/FIX_OFFLINE_DETECTION.md) for details.
|
||||
|
||||
### Usage
|
||||
|
||||
- Check the Settings page for details.
|
||||
|
||||
@@ -25,3 +25,11 @@ To assign a meaningful device name, the plugin resolves it in the following orde
|
||||
- **Comment**: The `comment` field in the MikroTik router's DHCP lease configuration. This is useful for naming static leases of known devies.
|
||||
- **Hostname**: The hostname provided by the device during DHCP negotiation.
|
||||
- **"(unknown)"**: as the fallback name, allowing other plugins to resolve the device name later.
|
||||
|
||||
|
||||
### Other info
|
||||
|
||||
- Version: 1.0
|
||||
- Author: [lookflying](https://github.com/lookflying)
|
||||
- Maintainer(s): [elraro](https://github.com/elraro), [kamil-olszewski-devskiller](https://github.com/kamil-olszewski-devskiller)
|
||||
- Release Date: 12-Sep-2024
|
||||
|
||||
@@ -178,7 +178,7 @@ def main():
|
||||
if file_name != 'last_result.log':
|
||||
mylog('verbose', [f'[{pluginName}] Processing: "{file_name}"'])
|
||||
|
||||
# make sure the file has teh correct name (e.g last_result.encoded.Node_1.1.log) to skip any otehr plugin files
|
||||
# make sure the file has the correct name (e.g last_result.encoded.Node_1.1.log) to skip any otehr plugin files
|
||||
if len(file_name.split('.')) > 2:
|
||||
# Store e.g. Node_1 from last_result.encoded.Node_1.1.log
|
||||
syncHubNodeName = file_name.split('.')[1]
|
||||
@@ -210,9 +210,10 @@ def main():
|
||||
existing_mac_addresses = set(row[0] for row in cursor.fetchall())
|
||||
|
||||
|
||||
# insert devices into the lats_result.log to manage state
|
||||
# insert devices into the last_result.log and thus CurrentScan table to manage state
|
||||
for device in device_data:
|
||||
if device['devPresentLastScan'] == 1:
|
||||
# only insert devices taht were online and skip the root node to prevent IP flipping on the hub
|
||||
if device['devPresentLastScan'] == 1 and str(device['devMac']).lower() != 'internet':
|
||||
plugin_objects.add_object(
|
||||
primaryId = device['devMac'],
|
||||
secondaryId = device['devLastIP'],
|
||||
|
||||
@@ -213,6 +213,33 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"function": "DEFAULT_PAGE_SIZE",
|
||||
"type": {
|
||||
"dataType": "integer",
|
||||
"elements": [
|
||||
{
|
||||
"elementType": "input",
|
||||
"elementOptions": [{ "type": "number" }],
|
||||
"transformers": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"maxLength": 50,
|
||||
"default_value": 20,
|
||||
"options": [],
|
||||
"localized": [],
|
||||
"name": [
|
||||
{
|
||||
"string": "Default page size"
|
||||
}
|
||||
],
|
||||
"description": [
|
||||
{
|
||||
"string": "Default number of items shown in tables per page, for example in teh Devices lists."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"function": "DEV_SECTIONS",
|
||||
"type": {
|
||||
|
||||
@@ -112,7 +112,12 @@ def get_device_data(site, api):
|
||||
mylog('verbose', [f'[{pluginName}] Site: {site_name} clients: {json.dumps(clients_resp, indent=2)}'])
|
||||
|
||||
# Build a lookup for devices by their 'id' to find parent MAC easily
|
||||
device_id_to_mac = {dev['id']: dev.get('macAddress', '') for dev in unifi_devices}
|
||||
device_id_to_mac = {}
|
||||
for dev in unifi_devices:
|
||||
if "id" not in dev:
|
||||
mylog("verbose", [f"[{pluginName}] Skipping device without 'id': {json.dumps(dev)}"])
|
||||
continue
|
||||
device_id_to_mac[dev["id"]] = dev.get("macAddress", "")
|
||||
|
||||
# Helper to resolve uplinkDeviceId to parent MAC, or "Internet" if no uplink
|
||||
def resolve_parent_mac(uplink_id):
|
||||
|
||||
@@ -566,122 +566,6 @@ $settingsJSON_DB = json_encode($settings, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX
|
||||
setCodeName = set["setKey"]
|
||||
|
||||
settingsArray = collectSetting(prefix, setCodeName, setType, settingsArray)
|
||||
|
||||
// // console.log(prefix);
|
||||
|
||||
// const setTypeObject = JSON.parse(processQuotes(setType))
|
||||
// // console.log(setTypeObject);
|
||||
|
||||
// const dataType = setTypeObject.dataType;
|
||||
|
||||
// // get the element with the input value(s)
|
||||
// let elements = setTypeObject.elements.filter(element => element.elementHasInputValue === 1);
|
||||
|
||||
// // if none found, take last
|
||||
// if(elements.length == 0)
|
||||
// {
|
||||
// elementWithInputValue = setTypeObject.elements[setTypeObject.elements.length - 1]
|
||||
// } else
|
||||
// {
|
||||
// elementWithInputValue = elements[0]
|
||||
// }
|
||||
|
||||
// const { elementType, elementOptions = [], transformers = [] } = elementWithInputValue;
|
||||
// const {
|
||||
// inputType,
|
||||
// readOnly,
|
||||
// isMultiSelect,
|
||||
// isOrdeable,
|
||||
// cssClasses,
|
||||
// placeholder,
|
||||
// suffix,
|
||||
// sourceIds,
|
||||
// separator,
|
||||
// editable,
|
||||
// valRes,
|
||||
// getStringKey,
|
||||
// onClick,
|
||||
// onChange,
|
||||
// customParams,
|
||||
// customId,
|
||||
// columns,
|
||||
// base64Regex,
|
||||
// elementOptionsBase64
|
||||
// } = handleElementOptions('none', elementOptions, transformers, val = "");
|
||||
|
||||
// let value;
|
||||
|
||||
// if (dataType === "string" && elementWithInputValue.elementType === "datatable" ) {
|
||||
|
||||
// value = collectTableData(`#${setCodeName}_table`)
|
||||
// settingsArray.push([prefix, setCodeName, dataType, btoa(JSON.stringify(value))]);
|
||||
|
||||
// } else if (dataType === "string" ||
|
||||
// (dataType === "integer" && (inputType === "number" || inputType === "text"))) {
|
||||
|
||||
// value = $('#' + setCodeName).val();
|
||||
// value = applyTransformers(value, transformers);
|
||||
|
||||
// settingsArray.push([prefix, setCodeName, dataType, value]);
|
||||
|
||||
// } else if (inputType === 'checkbox') {
|
||||
|
||||
// value = $(`#${setCodeName}`).is(':checked') ? 1 : 0;
|
||||
|
||||
// if(dataType === "boolean")
|
||||
// {
|
||||
// value = value == 1 ? "True" : "False";
|
||||
// }
|
||||
|
||||
// value = applyTransformers(value, transformers);
|
||||
// settingsArray.push([prefix, setCodeName, dataType, value]);
|
||||
|
||||
// } else if (dataType === "array" ) {
|
||||
|
||||
// let temps = [];
|
||||
|
||||
// if(isOrdeable)
|
||||
// {
|
||||
// temps = $(`#${setCodeName}`).val()
|
||||
// } else
|
||||
// {
|
||||
// // make sure to collect all if set as "editable" or selected only otherwise
|
||||
// $(`#${setCodeName}`).attr("my-editable") == "true" ? additionalSelector = "" : additionalSelector = ":selected";
|
||||
|
||||
// $(`#${setCodeName} option${additionalSelector}`).each(function() {
|
||||
// const vl = $(this).val();
|
||||
// if (vl !== '') {
|
||||
// temps.push(applyTransformers(vl, transformers));
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
|
||||
// value = JSON.stringify(temps);
|
||||
|
||||
// settingsArray.push([prefix, setCodeName, dataType, value]);
|
||||
|
||||
|
||||
// } else if (dataType === "none") {
|
||||
// // no value to save
|
||||
// value = ""
|
||||
// settingsArray.push([prefix, setCodeName, dataType, value]);
|
||||
|
||||
// } else if (dataType === "json") {
|
||||
|
||||
// value = $('#' + setCodeName).val();
|
||||
// value = applyTransformers(value, transformers);
|
||||
// value = JSON.stringify(value, null, 2)
|
||||
// settingsArray.push([prefix, setCodeName, dataType, value]);
|
||||
|
||||
// } else {
|
||||
|
||||
// console.error(`[saveSettings] Couldn't determine how to handle (setCodeName|dataType|inputType):(${setCodeName}|${dataType}|${inputType})`);
|
||||
|
||||
// value = $('#' + setCodeName).val();
|
||||
// value = applyTransformers(value, transformers);
|
||||
// console.error(`[saveSettings] Saving value "${value}"`);
|
||||
// settingsArray.push([prefix, setCodeName, dataType, value]);
|
||||
// }
|
||||
});
|
||||
|
||||
// sanity check to make sure settings were loaded & collected correctly
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
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';
|
||||
require_once $_SERVER['DOCUMENT_ROOT'] . '/php/templates/globals.php';
|
||||
?>
|
||||
|
||||
<?php
|
||||
|
||||
@@ -74,6 +74,8 @@ require 'php/templates/header.php';
|
||||
|
||||
$(document).ready(function() {
|
||||
const table = $('#notificationsTable').DataTable({
|
||||
"pageLength": parseInt(getSetting("UI_DEFAULT_PAGE_SIZE")),
|
||||
'lengthMenu' : getLengthMenu(parseInt(getSetting("UI_DEFAULT_PAGE_SIZE"))),
|
||||
"columns": [
|
||||
{ "data": "timestamp" ,
|
||||
"render": function(data, type, row) {
|
||||
|
||||
Reference in New Issue
Block a user