mirror of
https://github.com/jokob-sk/NetAlertX.git
synced 2026-09-14 07:10:05 -04:00
tz/LOCALE were evaluated once as module-level consts at script-load time. clearCache() (the header reload button) wipes localStorage right before reloading, so that reload could read an empty settings cache, fall back to the hardcoded 'Europe/Berlin' default, and stay locked to that wrong value for the rest of the page's life. Moved the reads inside localizeTimestamp() so they're evaluated fresh on every call instead.
1452 lines
42 KiB
JavaScript
Executable File
1452 lines
42 KiB
JavaScript
Executable File
/* -----------------------------------------------------------------------------
|
|
* NetAlertX
|
|
* Open Source Network Guard / WIFI & LAN intrusion detector
|
|
*
|
|
* common.js - Front module. Common Javascript functions
|
|
*-------------------------------------------------------------------------------
|
|
# Puche 2021 / 2022+ jokob support@netalertx.com GNU GPLv3
|
|
----------------------------------------------------------------------------- */
|
|
|
|
// -----------------------------------------------------------------------------
|
|
var timerRefreshData = ''
|
|
|
|
var emptyArr = ['undefined', "", undefined, null, 'null'];
|
|
var UI_LANG = "English (en_us)";
|
|
// allLanguages is populated at init via fetchAllLanguages() from GET /languages.
|
|
// Do not hardcode this list — add new languages to languages.json instead.
|
|
let allLanguages = [];
|
|
var settingsJSON = {}
|
|
|
|
// NAX_CACHE_VERSION and CACHE_KEYS moved to cache.js
|
|
|
|
|
|
// getCache, setCache, fetchJson, getAuthContext moved to cache.js
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// Fetch the canonical language list from GET /languages and populate allLanguages.
|
|
// Must be called after the API token is available (e.g. alongside cacheStrings).
|
|
// -----------------------------------------------------------------------------
|
|
function fetchAllLanguages(apiToken) {
|
|
return fetch('/languages', {
|
|
headers: { 'Authorization': 'Bearer ' + apiToken }
|
|
})
|
|
.then(function(resp) { return resp.json(); })
|
|
.then(function(data) {
|
|
if (data && data.success && Array.isArray(data.languages)) {
|
|
allLanguages = data.languages.map(function(l) { return l.code; });
|
|
}
|
|
})
|
|
.catch(function(err) {
|
|
console.warn('[fetchAllLanguages] Failed to load language list:', err);
|
|
});
|
|
}
|
|
|
|
|
|
// -----------------------------------------------------------------------------
|
|
function setCookie (cookie, value, expirationMinutes='') {
|
|
// Calc expiration date
|
|
var expires = '';
|
|
if (typeof expirationMinutes === 'number') {
|
|
expires = ';expires=' + new Date(Date.now() + expirationMinutes *60*1000).toUTCString();
|
|
}
|
|
|
|
// Save Cookie
|
|
document.cookie = cookie + "=" + value + expires;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
function getCookie (cookie) {
|
|
// Array of cookies
|
|
var allCookies = document.cookie.split(';');
|
|
|
|
// For each cookie
|
|
for (var i = 0; i < allCookies.length; i++) {
|
|
var currentCookie = allCookies[i].trim();
|
|
|
|
// If the current cookie is the correct cookie
|
|
if (currentCookie.indexOf (cookie +'=') == 0) {
|
|
// Return value
|
|
return currentCookie.substring (cookie.length+1);
|
|
}
|
|
}
|
|
|
|
// Return empty (not found)
|
|
return "";
|
|
}
|
|
|
|
|
|
// -----------------------------------------------------------------------------
|
|
function deleteCookie (cookie) {
|
|
document.cookie = cookie + '=;expires=Thu, 01 Jan 1970 00:00:00 UTC';
|
|
}
|
|
|
|
|
|
|
|
|
|
// cacheApiConfig, cacheSettings, getSettingOptions, getSetting moved to cache.js
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// cacheStrings, getString, getLangCode moved to cache.js
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// DateTime utilities
|
|
// -----------------------------------------------------------------------------
|
|
function localizeTimestamp(input) {
|
|
|
|
// Read fresh on every call (not a module-level const): getSetting() reads
|
|
// a localStorage cache that clearCache() wipes before reloading, so a
|
|
// stale/hardcoded fallback could otherwise stick for the page's lifetime. See #1640.
|
|
const tz = getSetting("TIMEZONE") || 'Europe/Berlin';
|
|
const LOCALE = getSetting('UI_LOCALE') || 'en-GB';
|
|
|
|
input = String(input || '').trim();
|
|
|
|
// 1. Unix timestamps (10 or 13 digits)
|
|
if (/^\d+$/.test(input)) {
|
|
const ms = input.length === 10 ? parseInt(input, 10) * 1000 : parseInt(input, 10);
|
|
return new Intl.DateTimeFormat('default', {
|
|
timeZone: tz,
|
|
year: 'numeric', month: '2-digit', day: '2-digit',
|
|
hour: '2-digit', minute: '2-digit', second: '2-digit',
|
|
hour12: false
|
|
}).format(new Date(ms));
|
|
}
|
|
|
|
// 2. European DD/MM/YYYY
|
|
let match = input.match(/^(\d{1,2})\/(\d{1,2})\/(\d{4})(?:[ ,]+(\d{1,2}:\d{2}(?::\d{2})?))?$/);
|
|
if (match) {
|
|
let [, d, m, y, t = "00:00:00", tzPart = ""] = match;
|
|
const dNum = parseInt(d, 10);
|
|
const mNum = parseInt(m, 10);
|
|
|
|
if (dNum <= 12 && mNum > 12) {
|
|
} else {
|
|
const iso = `${y}-${m.padStart(2,'0')}-${d.padStart(2,'0')}T${t.length===5 ? t + ":00" : t}${tzPart}`;
|
|
return formatSafe(iso, tz);
|
|
}
|
|
}
|
|
|
|
// 3. US MM/DD/YYYY
|
|
match = input.match(/^(\d{1,2})\/(\d{1,2})\/(\d{4})(?:[ ,]+(\d{1,2}:\d{2}(?::\d{2})?))?(.*)$/);
|
|
if (match) {
|
|
let [, m, d, y, t = "00:00:00", tzPart = ""] = match;
|
|
const iso = `${y}-${m.padStart(2,'0')}-${d.padStart(2,'0')}T${t.length===5?t+":00":t}${tzPart}`;
|
|
return formatSafe(iso, tz);
|
|
}
|
|
|
|
// 4. ISO YYYY-MM-DD with optional Z/+offset
|
|
match = input.match(/^(\d{4})-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])[ T](\d{1,2}:\d{2}(?::\d{2})?)(Z|[+-]\d{2}:?\d{2})?$/);
|
|
if (match) {
|
|
let [, y, m, d, time, offset = ""] = match;
|
|
const iso = `${y}-${m}-${d}T${time.length===5?time+":00":time}${offset}`;
|
|
return formatSafe(iso, tz);
|
|
}
|
|
|
|
// 5. RFC2822 / "25 Aug 2025 13:45:22 +0200"
|
|
match = input.match(/^\d{1,2} [A-Za-z]{3,} \d{4}/);
|
|
if (match) {
|
|
return formatSafe(input, tz);
|
|
}
|
|
|
|
// 6. DD-MM-YYYY with optional time
|
|
match = input.match(/^(\d{1,2})-(\d{1,2})-(\d{4})(?:[ T](\d{1,2}:\d{2}(?::\d{2})?))?$/);
|
|
if (match) {
|
|
let [, d, m, y, time = "00:00:00"] = match;
|
|
const iso = `${y}-${m.padStart(2,'0')}-${d.padStart(2,'0')}T${time.length===5?time+":00":time}`;
|
|
return formatSafe(iso, tz);
|
|
}
|
|
|
|
// 7. Strict YYYY-DD-MM with optional time
|
|
match = input.match(/^(\d{4})-(0[1-9]|[12]\d|3[01])-(0[1-9]|1[0-2])(?:[ T](\d{1,2}:\d{2}(?::\d{2})?))?$/);
|
|
if (match) {
|
|
let [, y, d, m, time = "00:00:00"] = match;
|
|
const iso = `${y}-${m}-${d}T${time.length === 5 ? time + ":00" : time}`;
|
|
return formatSafe(iso, tz);
|
|
}
|
|
|
|
// 8. Fallback
|
|
return formatSafe(input, tz);
|
|
|
|
function formatSafe(str, tz) {
|
|
|
|
// CHECK: Does the input string have timezone information?
|
|
// - Ends with Z: "2026-02-11T11:37:02Z"
|
|
// - Has GMT±offset: "Wed Feb 11 2026 12:34:12 GMT+1100 (...)"
|
|
// - Has offset at end: "2026-02-11 11:37:02+11:00"
|
|
// - Has timezone name in parentheses: "(Australian Eastern Daylight Time)"
|
|
const hasOffset = /Z$/i.test(str.trim()) ||
|
|
/GMT[+-]\d{2,4}/.test(str) ||
|
|
/[+-]\d{2}:?\d{2}$/.test(str.trim()) ||
|
|
/\([^)]+\)$/.test(str.trim());
|
|
|
|
// ⚠️ CRITICAL: All DB timestamps are stored in UTC without timezone markers.
|
|
// If no offset is present, we must explicitly mark it as UTC by appending 'Z'
|
|
// so JavaScript doesn't interpret it as local browser time.
|
|
let isoStr = str.trim();
|
|
if (!hasOffset) {
|
|
// Ensure proper ISO format before appending Z
|
|
// Replace space with 'T' if needed: "2026-02-11 11:37:02" → "2026-02-11T11:37:02Z"
|
|
isoStr = isoStr.trim().replace(/^(\d{4}-\d{2}-\d{2}) (\d{2}:\d{2}:\d{2})$/, '$1T$2') + 'Z';
|
|
}
|
|
|
|
const date = new Date(isoStr);
|
|
if (!isFinite(date)) {
|
|
console.error(`ERROR: Couldn't parse date: '${str}' with TIMEZONE ${tz}`);
|
|
return 'Failed conversion';
|
|
}
|
|
|
|
return new Intl.DateTimeFormat(LOCALE, {
|
|
// Convert from UTC to user's configured timezone
|
|
timeZone: tz,
|
|
year: 'numeric', month: '2-digit', day: '2-digit',
|
|
hour: '2-digit', minute: '2-digit', second: '2-digit',
|
|
hour12: false
|
|
}).format(date);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Returns start and end date for a given period.
|
|
* @param {string} period - "1 day", "7 days", "1 month", "1 year", "100 years"
|
|
* @returns {{start: string, end: string}} - Dates in "YYYY-MM-DD HH:MM:SS" format
|
|
*/
|
|
function getPeriodStartEnd(period) {
|
|
const now = new Date();
|
|
let start = new Date(now); // default start = now
|
|
let end = new Date(now); // default end = now
|
|
|
|
switch (period) {
|
|
case "1 day":
|
|
start.setDate(now.getDate() - 1);
|
|
break;
|
|
case "7 days":
|
|
start.setDate(now.getDate() - 7);
|
|
break;
|
|
case "1 month":
|
|
start.setMonth(now.getMonth() - 1);
|
|
break;
|
|
case "1 year":
|
|
start.setFullYear(now.getFullYear() - 1);
|
|
break;
|
|
case "100 years":
|
|
start = new Date(0); // very old date for "all"
|
|
break;
|
|
default:
|
|
console.warn("Unknown period, using 1 month as default");
|
|
start.setMonth(now.getMonth() - 1);
|
|
}
|
|
|
|
// Helper function to format date as "YYYY-MM-DD HH:MM:SS"
|
|
const formatDate = (date) => {
|
|
const pad = (n) => String(n).padStart(2, "0");
|
|
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ` +
|
|
`${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`;
|
|
};
|
|
|
|
return {
|
|
start: formatDate(start),
|
|
end: formatDate(end)
|
|
};
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// String utilities
|
|
// -----------------------------------------------------------------------------
|
|
|
|
|
|
// ----------------------------------------------------
|
|
/**
|
|
* Replaces double quotes within single-quoted strings, then converts all single quotes to double quotes,
|
|
* while preserving the intended structure.
|
|
*
|
|
* @param {string} inputString - The input string to process.
|
|
* @returns {string} - The processed string with transformations applied.
|
|
*/
|
|
function processQuotes(inputString) {
|
|
// Step 1: Replace double quotes within single-quoted strings
|
|
let tempString = inputString.replace(/'([^']*?)'/g, (match, p1) => {
|
|
const escapedContent = p1.replace(/"/g, '_escaped_double_quote_'); // Temporarily replace double quotes
|
|
return `'${escapedContent}'`;
|
|
});
|
|
|
|
// Step 2: Replace all single quotes with double quotes
|
|
tempString = tempString.replace(/'/g, '"');
|
|
|
|
// Step 3: Restore escaped double quotes
|
|
const processedString = tempString.replace(/_escaped_double_quote_/g, "'");
|
|
|
|
return processedString;
|
|
}
|
|
|
|
// ----------------------------------------------------
|
|
function jsonSyntaxHighlight(json) {
|
|
if (typeof json != 'string') {
|
|
json = JSON.stringify(json, undefined, 2);
|
|
}
|
|
json = json.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
return json.replace(/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g, function (match) {
|
|
var cls = 'number';
|
|
if (/^"/.test(match)) {
|
|
if (/:$/.test(match)) {
|
|
cls = 'key';
|
|
} else {
|
|
cls = 'string';
|
|
}
|
|
} else if (/true|false/.test(match)) {
|
|
cls = 'boolean';
|
|
} else if (/null/.test(match)) {
|
|
cls = 'null';
|
|
}
|
|
return '<span class="' + cls + '">' + match + '</span>';
|
|
});
|
|
}
|
|
|
|
// ----------------------------------------------------
|
|
function isValidBase64(str) {
|
|
// Base64 characters set
|
|
var base64CharacterSet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
|
|
// Remove all valid characters from the string
|
|
var invalidCharacters = str.replace(new RegExp('[' + base64CharacterSet + ']', 'g'), '');
|
|
// If there are any characters left, the string is invalid
|
|
return invalidCharacters === '';
|
|
}
|
|
|
|
// -------------------------------------------------------------------
|
|
// Utility function to check if the value is already Base64
|
|
function isBase64(value) {
|
|
if (typeof value !== "string" || value.trim() === "") return false;
|
|
|
|
// Must have valid length
|
|
if (value.length % 4 !== 0) return false;
|
|
|
|
// Valid Base64 characters
|
|
const base64Regex = /^[A-Za-z0-9+/]+={0,2}$/;
|
|
if (!base64Regex.test(value)) return false;
|
|
|
|
|
|
try {
|
|
const decoded = atob(value);
|
|
|
|
// Re-encode
|
|
const reencoded = btoa(decoded);
|
|
|
|
if (reencoded !== value) return false;
|
|
|
|
// Extra verification:
|
|
// Ensure decoding didn't silently drop bytes (atob bug)
|
|
// Encode raw bytes: check if large char codes exist (invalid UTF-16)
|
|
for (let i = 0; i < decoded.length; i++) {
|
|
const code = decoded.charCodeAt(i);
|
|
if (code > 255) return false; // invalid binary byte
|
|
}
|
|
|
|
return true;
|
|
} catch (e) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
|
|
// ----------------------------------------------------
|
|
function isValidJSON(jsonString) {
|
|
try {
|
|
JSON.parse(jsonString);
|
|
return true;
|
|
} catch (e) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// ----------------------------------------------------
|
|
/**
|
|
* Encode special HTML characters into HTML entities.
|
|
*
|
|
* Prevents HTML injection/XSS when displaying untrusted text
|
|
* inside HTML content contexts.
|
|
*
|
|
* Example:
|
|
* <script> -> <script>
|
|
*
|
|
* Note:
|
|
* - Intended for HTML text contexts only
|
|
* - Prefer using textContent or jQuery .text() when possible
|
|
* - Do NOT use as the sole protection for inline JS, URLs, CSS,
|
|
* or unsafe innerHTML usage
|
|
*
|
|
* @param {*} str - Value to encode
|
|
* @returns {string} Encoded safe HTML string
|
|
*/
|
|
function encodeSpecialChars(str) {
|
|
|
|
if (str === null || str === undefined) {
|
|
return '';
|
|
}
|
|
|
|
str = String(str);
|
|
|
|
return str
|
|
.replace(/&/g, '&')
|
|
.replace(/</g, '<')
|
|
.replace(/>/g, '>')
|
|
.replace(/"/g, '"')
|
|
.replace(/'/g, ''');
|
|
}
|
|
// ----------------------------------------------------
|
|
/**
|
|
* Decode HTML entities back into normal characters.
|
|
*
|
|
* Example:
|
|
* <script> -> <script>
|
|
*
|
|
* Warning:
|
|
* Decoding untrusted content and later inserting it into
|
|
* innerHTML or other HTML contexts can reintroduce XSS risks.
|
|
*
|
|
* @param {*} str - Value to decode
|
|
* @returns {string} Decoded string
|
|
*/
|
|
function decodeSpecialChars(str) {
|
|
|
|
if (str === null || str === undefined) {
|
|
return '';
|
|
}
|
|
|
|
str = String(str);
|
|
|
|
return str
|
|
.replace(/&/g, '&')
|
|
.replace(/</g, '<')
|
|
.replace(/>/g, '>')
|
|
.replace(/"/g, '"')
|
|
.replace(/'/g, '\'');
|
|
}
|
|
|
|
// ----------------------------------------------------
|
|
// base64 conversion of UTF8 chars
|
|
function utf8ToBase64(str) {
|
|
// Convert the string to a Uint8Array using TextEncoder
|
|
const utf8Bytes = new TextEncoder().encode(str);
|
|
|
|
// Convert the Uint8Array to a base64-encoded string
|
|
return btoa(String.fromCharCode(...utf8Bytes));
|
|
}
|
|
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// General utilities
|
|
// -----------------------------------------------------------------------------
|
|
|
|
// check if JSON object
|
|
function isJsonObject(value) {
|
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
}
|
|
|
|
|
|
// remove unnecessary lines from the result
|
|
function sanitize(data)
|
|
{
|
|
return data.replace(/(\r\n|\n|\r)/gm,"").replace(/[^\x00-\x7F]/g, "")
|
|
}
|
|
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// Check and handle locked database
|
|
function handle_locked_DB(data)
|
|
{
|
|
if(data.includes('database is locked'))
|
|
{
|
|
// console.log(data)
|
|
showSpinner()
|
|
|
|
setTimeout(function() {
|
|
console.warn("Database locked - reload")
|
|
location.reload();
|
|
}, 5000);
|
|
}
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
function numberArrayFromString(data)
|
|
{
|
|
data = JSON.parse(sanitize(data));
|
|
return data.replace(/\[|\]/g, '').split(',').map(Number);
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// Update network parent/child relationship (network tree)
|
|
function updateNetworkLeaf(leafMac, parentMac) {
|
|
const { apiBase, authHeader } = getAuthContext();
|
|
const url = `${apiBase}/device/${leafMac}/update-column`;
|
|
|
|
$.ajax({
|
|
method: "POST",
|
|
url: url,
|
|
headers: authHeader,
|
|
data: JSON.stringify({ columnName: "devParentMAC", columnValue: parentMac }),
|
|
contentType: "application/json",
|
|
success: function(response) {
|
|
if(response.success) {
|
|
showMessage("Saved");
|
|
// Remove navigation prompt "Are you sure you want to leave..."
|
|
window.onbeforeunload = null;
|
|
} else {
|
|
showMessage("ERROR: " + (response.error || "Unknown error"));
|
|
}
|
|
},
|
|
error: function(xhr, status, error) {
|
|
console.error("Error updating network leaf:", status, error);
|
|
showMessage("ERROR: " + (xhr.responseJSON?.error || error));
|
|
}
|
|
});
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// Legacy function wrapper for backward compatibility
|
|
function saveData(functionName, id, value) {
|
|
if (functionName === 'updateNetworkLeaf') {
|
|
updateNetworkLeaf(id, value);
|
|
} else {
|
|
console.warn("saveData called with unknown functionName:", functionName);
|
|
showMessage("ERROR: Unknown function");
|
|
}
|
|
}
|
|
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// create a link to the device
|
|
function createDeviceLink(input)
|
|
{
|
|
if(checkMacOrInternet(input))
|
|
{
|
|
return `<span class="anonymizeMac"><a href="/deviceDetails.php?mac=${input}" target="_blank">${encodeSpecialChars(getDevDataByMac(input, "devName"))}</a><span>`
|
|
}
|
|
|
|
return input;
|
|
}
|
|
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// remove an item from an array
|
|
function removeItemFromArray(arr, value) {
|
|
var index = arr.indexOf(value);
|
|
if (index > -1) {
|
|
arr.splice(index, 1);
|
|
}
|
|
return arr;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
function sleep(milliseconds) {
|
|
const date = Date.now();
|
|
let currentDate = null;
|
|
do {
|
|
currentDate = Date.now();
|
|
} while (currentDate - date < milliseconds);
|
|
}
|
|
|
|
// ---------------------------------------------------------
|
|
somethingChanged = false;
|
|
function settingsChanged()
|
|
{
|
|
somethingChanged = true;
|
|
// Enable navigation prompt ... "Are you sure you want to leave..."
|
|
window.onbeforeunload = function() {
|
|
return true;
|
|
};
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// Get Anchor from URL
|
|
function getUrlAnchor(defaultValue){
|
|
|
|
target = defaultValue
|
|
|
|
var url = window.location.href;
|
|
if (url.includes("#")) {
|
|
|
|
// default selection
|
|
selectedTab = defaultValue
|
|
|
|
// the #target from the url
|
|
target = window.location.hash.substr(1)
|
|
|
|
// get only the part between #...?
|
|
if(target.includes('?'))
|
|
{
|
|
target = target.split('?')[0]
|
|
}
|
|
|
|
return target
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// get query string from URL
|
|
function getQueryString(key){
|
|
params = new Proxy(new URLSearchParams(window.location.search), {
|
|
get: (searchParams, prop) => searchParams.get(prop),
|
|
});
|
|
|
|
tmp = params[key]
|
|
|
|
if(emptyArr.includes(tmp))
|
|
{
|
|
var queryParams = {};
|
|
fullUrl = window.location.toString();
|
|
|
|
// console.log(fullUrl);
|
|
|
|
if (fullUrl.includes('?')) {
|
|
var queryString = fullUrl.split('?')[1];
|
|
|
|
// Split the query string into individual parameters
|
|
var paramsArray = queryString.split('&');
|
|
|
|
// Loop through the parameters array
|
|
paramsArray.forEach(function(param) {
|
|
// Split each parameter into key and value
|
|
var keyValue = param.split('=');
|
|
var keyTmp = decodeURIComponent(keyValue[0]);
|
|
var value = decodeURIComponent(keyValue[1] || '');
|
|
|
|
// Store key-value pair in the queryParams object
|
|
queryParams[keyTmp] = value;
|
|
});
|
|
}
|
|
|
|
// console.log(queryParams);
|
|
|
|
tmp = queryParams[key]
|
|
}
|
|
|
|
result = emptyArr.includes(tmp) ? "" : tmp;
|
|
|
|
return result
|
|
}
|
|
// -----------------------------------------------------------------------------
|
|
function translateHTMLcodes (text) {
|
|
if (text == null || emptyArr.includes(text)) {
|
|
return null;
|
|
} else if (typeof text === 'string' || text instanceof String)
|
|
{
|
|
var text2 = text.replace(new RegExp(' ', 'g'), " ");
|
|
text2 = text2.replace(new RegExp('<', 'g'), "<");
|
|
return text2;
|
|
}
|
|
|
|
return "";
|
|
}
|
|
|
|
|
|
// -----------------------------------------------------------------------------
|
|
function stopTimerRefreshData () {
|
|
try {
|
|
clearTimeout (timerRefreshData);
|
|
} catch (e) {}
|
|
}
|
|
|
|
|
|
// -----------------------------------------------------------------------------
|
|
function newTimerRefreshData (refeshFunction, timeToRefresh) {
|
|
|
|
if(timeToRefresh && (timeToRefresh != 0 || timeToRefresh != ""))
|
|
{
|
|
time = parseInt(timeToRefresh)
|
|
} else
|
|
{
|
|
time = 60000
|
|
}
|
|
|
|
timerRefreshData = setTimeout (function() {
|
|
refeshFunction();
|
|
}, time);
|
|
}
|
|
|
|
|
|
// -----------------------------------------------------------------------------
|
|
function debugTimer () {
|
|
$('#pageTitle').html (new Date().getSeconds());
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
function secondsSincePageLoad() {
|
|
// Get the current time since the page was loaded
|
|
var timeSincePageLoad = performance.now();
|
|
|
|
// Convert milliseconds to seconds
|
|
var secondsAgo = Math.floor(timeSincePageLoad / 1000);
|
|
|
|
return secondsAgo;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// Open url in new tab
|
|
function openInNewTab (url) {
|
|
window.open(url, "_blank");
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// Navigate to URL if the current URL is not in the provided list of URLs
|
|
function openUrl(urls) {
|
|
var currentUrl = window.location.href;
|
|
var mainUrl = currentUrl.match(/^.*?(?=#|\?|$)/)[0]; // Extract main URL
|
|
|
|
var isMatch = false;
|
|
|
|
$.each(urls,function(index, obj){
|
|
|
|
// remove . for comaprison if in the string, e.g.: ./devices.php
|
|
arrayUrl = obj.replace('.','')
|
|
|
|
// check if we are on a url contained in the array
|
|
if(mainUrl.includes(arrayUrl))
|
|
{
|
|
isMatch = true;
|
|
}
|
|
});
|
|
|
|
// if we are not, redirect
|
|
if (isMatch == false) {
|
|
window.location.href = urls[0]; // Redirect to the first URL in the list if not found
|
|
}
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// force load URL in current window with specific anchor
|
|
function forceLoadUrl(relativeUrl) {
|
|
|
|
window.location.replace(relativeUrl);
|
|
window.location.reload()
|
|
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
function navigateToDeviceWithIp (ip) {
|
|
|
|
$.get('php/server/query_json.php', { file: 'table_devices.json', nocache: Date.now() }, function(res) {
|
|
|
|
devices = res["data"];
|
|
|
|
mac = ""
|
|
|
|
$.each(devices, function(index, obj) {
|
|
|
|
if(obj.devLastIP.trim() == ip.trim())
|
|
{
|
|
mac = obj.devMac;
|
|
|
|
window.open('./deviceDetails.php?mac=' + mac , "_blank");
|
|
}
|
|
});
|
|
|
|
});
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// Check if MAC or Internet
|
|
function checkMacOrInternet(inputStr) {
|
|
// Regular expression pattern for matching a MAC address
|
|
const macPattern = /^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$/;
|
|
|
|
if (inputStr.toLowerCase() === 'internet') {
|
|
return true;
|
|
} else if (macPattern.test(inputStr)) {
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Alias
|
|
function isValidMac(value) {
|
|
return checkMacOrInternet(value);
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// Gte MAC from query string
|
|
function getMac(){
|
|
params = new Proxy(new URLSearchParams(window.location.search), {
|
|
get: (searchParams, prop) => searchParams.get(prop),
|
|
});
|
|
|
|
mac = params.mac;
|
|
|
|
if (mac == "") {
|
|
console.error("Couldn't retrieve mac");
|
|
}
|
|
|
|
return mac;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// A function used to make the IP address orderable
|
|
function isValidIPv6(ipAddress) {
|
|
// Regular expression for IPv6 validation
|
|
const ipv6Regex = /^([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}$|^([0-9a-fA-F]{1,4}:){1,7}:|^([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}$|^([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}$|^([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}$|^([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}$|^([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}$|^[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})$/;
|
|
|
|
return ipv6Regex.test(ipAddress);
|
|
}
|
|
|
|
function isValidIPv4(ip) {
|
|
const ipv4Regex = /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
|
|
return ipv4Regex.test(ip);
|
|
}
|
|
|
|
|
|
function formatIPlong(ipAddress) {
|
|
if (ipAddress.includes(':') && isValidIPv6(ipAddress)) {
|
|
const parts = ipAddress.split(':');
|
|
|
|
return parts.reduce((acc, part, index) => {
|
|
if (part === '') {
|
|
const remainingGroups = 8 - parts.length + 1;
|
|
return acc << (16 * remainingGroups);
|
|
}
|
|
|
|
const hexValue = parseInt(part, 16);
|
|
return acc | (hexValue << (112 - index * 16));
|
|
}, 0);
|
|
} else {
|
|
// Handle IPv4 address
|
|
const parts = ipAddress.split('.');
|
|
|
|
if (parts.length !== 4) {
|
|
console.log("⚠ Invalid IPv4 address: " + ipAddress);
|
|
return -1; // or any other default value indicating an error
|
|
}
|
|
|
|
return (parseInt(parts[0]) << 24) |
|
|
(parseInt(parts[1]) << 16) |
|
|
(parseInt(parts[2]) << 8) |
|
|
parseInt(parts[3]);
|
|
}
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// Check if MAC is a random one
|
|
function isRandomMAC(mac)
|
|
{
|
|
isRandom = false;
|
|
|
|
isRandom = ["2", "6", "A", "E", "a", "e"].includes(mac[1]);
|
|
|
|
// if detected as random, make sure it doesn't start with a prefix which teh suer doesn't want to mark as random
|
|
if(isRandom)
|
|
{
|
|
$.each(createArray(getSetting("UI_NOT_RANDOM_MAC")), function(index, prefix) {
|
|
|
|
if(mac.startsWith(prefix))
|
|
{
|
|
isRandom = false;
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
return isRandom;
|
|
}
|
|
|
|
// ---------------------------------------------------------
|
|
// Generate an array object from a string representation of an array
|
|
function createArray(input) {
|
|
// Is already array, return
|
|
if (Array.isArray(input)) {
|
|
return input;
|
|
}
|
|
// Empty array
|
|
// if (input === '[]' || input === '') {
|
|
if (input === '[]') {
|
|
return [];
|
|
}
|
|
// handle integer
|
|
if (typeof input === 'number') {
|
|
input = input.toString();
|
|
}
|
|
|
|
// Regex pattern for brackets
|
|
const patternBrackets = /(^\s*\[)|(\]\s*$)/g;
|
|
const replacement = '';
|
|
|
|
// Remove brackets
|
|
const noBrackets = input.replace(patternBrackets, replacement);
|
|
|
|
const options = [];
|
|
|
|
// Detect the type of quote used after the opening bracket
|
|
const firstChar = noBrackets.trim()[0];
|
|
const isDoubleQuoted = firstChar === '"';
|
|
const isSingleQuoted = firstChar === "'";
|
|
|
|
// Create array while handling commas within quoted segments
|
|
let currentSegment = '';
|
|
let withinQuotes = false;
|
|
for (let i = 0; i < noBrackets.length; i++) {
|
|
const char = noBrackets[i];
|
|
if ((char === '"' && !isSingleQuoted) || (char === "'" && !isDoubleQuoted)) {
|
|
withinQuotes = !withinQuotes;
|
|
}
|
|
if (char === ',' && !withinQuotes) {
|
|
options.push(currentSegment.trim());
|
|
currentSegment = '';
|
|
} else {
|
|
currentSegment += char;
|
|
}
|
|
}
|
|
// Push the last segment
|
|
options.push(currentSegment.trim());
|
|
|
|
// Remove quotes based on detected type
|
|
options.forEach((item, index) => {
|
|
let trimmedItem = item.trim();
|
|
// Check if the string starts and ends with the same type of quote
|
|
if ((isDoubleQuoted && trimmedItem.startsWith('"') && trimmedItem.endsWith('"')) ||
|
|
(isSingleQuoted && trimmedItem.startsWith("'") && trimmedItem.endsWith("'"))) {
|
|
// Remove the quotes
|
|
trimmedItem = trimmedItem.substring(1, trimmedItem.length - 1);
|
|
}
|
|
options[index] = trimmedItem;
|
|
});
|
|
|
|
return options;
|
|
}
|
|
|
|
// getDevDataByMac, cacheDevices, devicesListAll_JSON moved to cache.js
|
|
|
|
// -----------------------------------------------------------------------------
|
|
function isEmpty(value)
|
|
{
|
|
return emptyArr.includes(value)
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
function mergeUniqueArrays(arr1, arr2) {
|
|
let mergedArray = [...arr1]; // Make a copy of arr1
|
|
|
|
arr2.forEach(element => {
|
|
if (!mergedArray.includes(element)) {
|
|
mergedArray.push(element);
|
|
}
|
|
});
|
|
|
|
return mergedArray;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// Generate a GUID
|
|
function getGuid() {
|
|
return "10000000-1000-4000-8000-100000000000".replace(/[018]/g, c =>
|
|
(c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)
|
|
);
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// UI
|
|
// -----------------------------------------------------------------------------
|
|
// -----------------------------------------------------------------------------
|
|
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// Loading Spinner overlay
|
|
// -----------------------------------------------------------------------------
|
|
|
|
let spinnerTimeout = null;
|
|
let animationTime = 300
|
|
|
|
// show spinner on all clicks
|
|
$(document).on('click', 'a', function (e) {
|
|
const href = this.href;
|
|
|
|
if (
|
|
href &&
|
|
this.target !== '_blank' &&
|
|
!this.hasAttribute('download') &&
|
|
this.origin === location.origin &&
|
|
this.pathname + this.search !== location.pathname + location.search
|
|
) {
|
|
showSpinner();
|
|
}
|
|
});
|
|
|
|
function resolveSpinnerTarget(explicitTarget = null) {
|
|
if (explicitTarget) {
|
|
const explicit = $(explicitTarget);
|
|
if (explicit.length) {
|
|
return explicit;
|
|
}
|
|
}
|
|
|
|
// Prefer the active tab pane itself
|
|
const activePane = $(".tab-pane.active:visible");
|
|
if (activePane.length) {
|
|
// Use a direct child spinnerTarget if one exists and is visible
|
|
const childTarget = activePane.children(".spinnerTarget:visible").first();
|
|
return childTarget.length ? childTarget : activePane;
|
|
}
|
|
|
|
// Fall back to any visible spinnerTarget
|
|
const visibleTarget = $(".spinnerTarget:visible").first();
|
|
if (visibleTarget.length) {
|
|
return visibleTarget;
|
|
}
|
|
|
|
// Finally, cover the page
|
|
return $("body");
|
|
}
|
|
|
|
function showSpinner(stringKey = "Loading", target = null) {
|
|
let text = isEmpty(stringKey)
|
|
? "Loading..."
|
|
: getString(stringKey || "Loading");
|
|
|
|
if (!text || !text.trim()) {
|
|
text = "Loading...";
|
|
}
|
|
|
|
const spinner = $("#loadingSpinner");
|
|
const resolvedTarget = resolveSpinnerTarget(target);
|
|
|
|
console.log(resolvedTarget);
|
|
|
|
// console.log(`spinnerTarget=${resolvedTarget.attr("id")} class=${resolvedTarget.attr("class")} size=${resolvedTarget.outerWidth()}x${resolvedTarget.outerHeight()} parent=${resolvedTarget.parent().outerWidth()}x${resolvedTarget.parent().outerHeight()}`)
|
|
|
|
$("#loadingSpinnerText").text(text);
|
|
|
|
if (resolvedTarget.length) {
|
|
const offset = resolvedTarget.offset();
|
|
|
|
spinner.css({
|
|
position: "absolute",
|
|
top: offset.top,
|
|
left: offset.left,
|
|
width: resolvedTarget.outerWidth(),
|
|
height: resolvedTarget.outerHeight(),
|
|
zIndex: 9999
|
|
});
|
|
} else {
|
|
spinner.css({
|
|
position: "fixed",
|
|
top: 0,
|
|
left: 0,
|
|
width: "100%",
|
|
height: "100%",
|
|
zIndex: 9999
|
|
});
|
|
}
|
|
|
|
requestAnimationFrame(() => {
|
|
spinner.addClass("visible");
|
|
spinner.fadeIn(animationTime);
|
|
});
|
|
}
|
|
|
|
|
|
function hideSpinner() {
|
|
clearTimeout(spinnerTimeout);
|
|
const spinner = $("#loadingSpinner");
|
|
|
|
if (!spinner.length) return;
|
|
|
|
const target = $(".spinnerTarget").last();
|
|
|
|
if (target.length) {
|
|
// Lock position to target
|
|
const offset = target.offset();
|
|
const width = target.outerWidth();
|
|
const height = target.outerHeight();
|
|
|
|
spinner.css({
|
|
position: "absolute",
|
|
top: offset.top,
|
|
left: offset.left,
|
|
width: width,
|
|
height: height,
|
|
zIndex: 9999
|
|
});
|
|
} else {
|
|
// Fullscreen fallback
|
|
spinner.css({
|
|
position: "fixed",
|
|
top: 0,
|
|
left: 0,
|
|
width: "100%",
|
|
height: "100%",
|
|
zIndex: 9999
|
|
});
|
|
}
|
|
|
|
// Trigger fade-out and only remove styles AFTER fade completes AND display is none
|
|
spinner.removeClass("visible").fadeOut(animationTime, () => {
|
|
// Ensure it's really hidden before resetting styles
|
|
spinner.css({
|
|
display: "none"
|
|
});
|
|
|
|
spinner.css({
|
|
position: "",
|
|
top: "",
|
|
left: "",
|
|
width: "",
|
|
height: "",
|
|
zIndex: ""
|
|
});
|
|
});
|
|
}
|
|
|
|
|
|
// --------------------------------------------------------
|
|
// Calls a backend function to add a front-end event to an execution queue
|
|
function updateApi(apiEndpoints)
|
|
{
|
|
|
|
// value has to be in format event|param. e.g. run|ARPSCAN
|
|
action = `${getGuid()}|update_api|${apiEndpoints}`
|
|
|
|
const { token: apiToken, apiBase: apiBaseUrl, authHeader } = getAuthContext();
|
|
const url = `${apiBaseUrl}/logs/add-to-execution-queue`;
|
|
|
|
$.ajax({
|
|
method: "POST",
|
|
url: url,
|
|
headers: { ...authHeader, "Content-Type": "application/json" },
|
|
data: JSON.stringify({ action: action }),
|
|
success: function(data, textStatus) {
|
|
console.log(data)
|
|
}
|
|
})
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// handling smooth scrolling
|
|
// -----------------------------------------------------------------------------
|
|
function setupSmoothScrolling() {
|
|
// Function to scroll to the element
|
|
function scrollToElement(id) {
|
|
$('html, body').animate({
|
|
scrollTop: $("#" + id).offset().top - 50
|
|
}, 1000);
|
|
}
|
|
|
|
// Scroll to the element when clicking on anchor links
|
|
$('a[href*="#"]').on('click', function(event) {
|
|
var href = $(this).attr('href');
|
|
if (href !=='#' && href && href.includes('#') && !$(this).is('[data-toggle="collapse"]')) {
|
|
var id = href.substring(href.indexOf("#") + 1); // Get the ID from the href attribute
|
|
if ($("#" + id).length > 0) {
|
|
event.preventDefault(); // Prevent default anchor behavior
|
|
scrollToElement(id); // Scroll to the element
|
|
}
|
|
}
|
|
});
|
|
|
|
// Check if there's an ID in the URL and scroll to it
|
|
var url = window.location.href;
|
|
if (url.includes("#")) {
|
|
var idFromURL = url.substring(url.indexOf("#") + 1);
|
|
if (idFromURL != "" && $("#" + idFromURL).length > 0) {
|
|
scrollToElement(idFromURL);
|
|
}
|
|
}
|
|
}
|
|
|
|
// -------------------------------------------------------------------
|
|
// Function to check if options_params contains a parameter with type "sql"
|
|
function hasSqlType(params) {
|
|
for (let param of params) {
|
|
if (param.type === "sql") {
|
|
return true; // Found a parameter with type "sql"
|
|
}
|
|
}
|
|
return false; // No parameter with type "sql" found
|
|
}
|
|
|
|
// -------------------------------------------------------------------
|
|
// Function to check if string is SQL query
|
|
function isSQLQuery(query) {
|
|
// Regular expression to match common SQL keywords and syntax with word boundaries
|
|
var sqlRegex = /\b(SELECT|INSERT|UPDATE|DELETE|CREATE|DROP|ALTER|FROM|JOIN|WHERE|SET|VALUES|GROUP BY|ORDER BY|LIMIT)\b/i;
|
|
|
|
return sqlRegex.test(query);
|
|
}
|
|
|
|
/**
|
|
* Lazy tab initializer
|
|
* - Ensures a tab is only initialized once
|
|
* - Waits until tab is visible in DOM
|
|
* - Uses polling (compatible with your existing architecture)
|
|
*/
|
|
|
|
function waitForAppReady(cb) {
|
|
|
|
if (window.__appReady) {
|
|
cb();
|
|
return;
|
|
}
|
|
|
|
const t = setInterval(() => {
|
|
|
|
if (window.__appReady) {
|
|
clearInterval(t);
|
|
cb();
|
|
}
|
|
|
|
}, 50);
|
|
}
|
|
|
|
function initLazyTab(selector, initFn, intervalMs = 200) {
|
|
|
|
const stateKey = `__lazy_init_${selector}`;
|
|
let initialized = false;
|
|
|
|
function runner() {
|
|
|
|
// prevent duplicate execution
|
|
if (initialized || window[stateKey]) return;
|
|
|
|
const $el = $(selector);
|
|
|
|
// ---------------------------------------------------------
|
|
// CASE 1: element does not exist → standalone page variant
|
|
// run immediately (no DOM dependency)
|
|
// ---------------------------------------------------------
|
|
if (!$el.length) {
|
|
initialized = true;
|
|
window[stateKey] = true;
|
|
|
|
try {
|
|
initFn();
|
|
} catch (e) {
|
|
console.error("Lazy init (no-element) failed:", selector, e);
|
|
}
|
|
return;
|
|
}
|
|
|
|
// ---------------------------------------------------------
|
|
// CASE 2: element exists AND is visible → run immediately
|
|
// ---------------------------------------------------------
|
|
if ($el.is(':visible')) {
|
|
initialized = true;
|
|
window[stateKey] = true;
|
|
|
|
try {
|
|
initFn();
|
|
} catch (e) {
|
|
console.error("Lazy init (visible) failed:", selector, e);
|
|
}
|
|
return;
|
|
}
|
|
|
|
// ---------------------------------------------------------
|
|
// CASE 3: element exists but hidden → wait for visibility
|
|
// (tab mode)
|
|
// ---------------------------------------------------------
|
|
setTimeout(runner, intervalMs);
|
|
}
|
|
|
|
runner();
|
|
}
|
|
|
|
// -------------------------------------------------------------------
|
|
// Get corresponding plugin setting object
|
|
function getPluginSettingObject(pluginsData, setting_key, unique_prefix ) {
|
|
|
|
result = {}
|
|
unique_prefix == undefined ? unique_prefix = setting_key.split("_")[0] : unique_prefix = unique_prefix;
|
|
|
|
$.each(pluginsData, function (i, plgnObj){
|
|
// go thru plugins
|
|
if(plgnObj.unique_prefix == unique_prefix)
|
|
{
|
|
// go thru plugin settings
|
|
$.each(plgnObj["settings"], function (j, setObj){
|
|
|
|
if(`${unique_prefix}_${setObj.function}` == setting_key)
|
|
{
|
|
result = setObj
|
|
}
|
|
|
|
});
|
|
}
|
|
});
|
|
|
|
return result
|
|
}
|
|
|
|
// -------------------------------------------------------------------
|
|
// Resolve all option parameters
|
|
function resolveParams(params, template) {
|
|
params.forEach(param => {
|
|
// Check if the template includes the parameter name
|
|
if (template.includes("{" + param.name + "}")) {
|
|
// If the parameter type is 'setting', retrieve setting value
|
|
if (param.type == "setting") {
|
|
var value = getSetting(param.value);
|
|
|
|
// Remove brackets and single quotes, replace them with double quotes
|
|
value = value.replace('[','').replace(']','').replace(/'/g, '"');
|
|
|
|
// Split the string into an array, remove empty elements
|
|
const arr = value.split(',').filter(Boolean);
|
|
|
|
// Join the array elements with commas
|
|
const result = arr.join(', ');
|
|
|
|
// Replace placeholder with setting value
|
|
template = template.replace("{" + param.name + "}", result);
|
|
} else {
|
|
// If the parameter type is not 'setting', use the provided value
|
|
template = template.replace("{" + param.name + "}", param.value);
|
|
}
|
|
}
|
|
});
|
|
|
|
// Log the resolved template
|
|
// console.log(template);
|
|
|
|
// Return the resolved template
|
|
return template;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// check if two arrays contain same values even if out of order
|
|
function arraysContainSameValues(arr1, arr2) {
|
|
// Check if both parameters are arrays
|
|
if (!Array.isArray(arr1) || !Array.isArray(arr2)) {
|
|
return false;
|
|
} else
|
|
{
|
|
// Sort and stringify arrays, then compare
|
|
return JSON.stringify(arr1.slice().sort()) === JSON.stringify(arr2.slice().sort());
|
|
}
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// Hide elements on the page based on the supplied setting
|
|
function hideUIelements(setKey) {
|
|
|
|
hiddenSectionsSetting = getSetting(setKey)
|
|
|
|
if(hiddenSectionsSetting != "") // handle if settings not yet initialized
|
|
{
|
|
|
|
sectionsArray = createArray(hiddenSectionsSetting)
|
|
|
|
// remove spaces to get IDs
|
|
var newArray = $.map(sectionsArray, function(value) {
|
|
return value.replace(/\s/g, '');
|
|
});
|
|
|
|
$.each(newArray, function(index, hiddenSection) {
|
|
|
|
if($('#' + hiddenSection))
|
|
{
|
|
$('#' + hiddenSection).hide()
|
|
}
|
|
|
|
});
|
|
}
|
|
|
|
}
|
|
|
|
// ------------------------------------------------------------
|
|
function getDevicesList()
|
|
{
|
|
// Read cache (skip cookie expiry check)
|
|
const cached = getCache(CACHE_KEYS.DEVICES_ALL);
|
|
let devicesList = parseDeviceCache(cached);
|
|
|
|
// only loop thru the filtered down list
|
|
visibleDevices = getCache(CACHE_KEYS.VISIBLE_MACS)
|
|
|
|
if(visibleDevices != "") {
|
|
visibleDevicesMACs = visibleDevices.split(',');
|
|
|
|
devicesList_tmp = [];
|
|
|
|
// Iterate through the data and filter only visible devices
|
|
$.each(devicesList, function(index, item) {
|
|
// Check if the current item's MAC exists in visibleDevicesMACs
|
|
if (visibleDevicesMACs.includes(item.devMac)) {
|
|
devicesList_tmp.push(item);
|
|
}
|
|
});
|
|
|
|
// Update devicesList with the filtered items
|
|
devicesList = devicesList_tmp;
|
|
}
|
|
|
|
return devicesList;
|
|
}
|
|
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// apply theme
|
|
|
|
$(document).ready(function() {
|
|
let theme = getSetting("UI_theme");
|
|
if (theme) {
|
|
theme = theme.replace("['","").replace("']","");
|
|
// Add the theme stylesheet
|
|
setCookie("UI_theme", theme);
|
|
switch(theme) {
|
|
case "Dark":
|
|
$('head').append('<link rel="stylesheet" href="css/dark-patch.css">');
|
|
break;
|
|
case "System":
|
|
$('head').append('<link rel="stylesheet" href="css/system-dark-patch.css">');
|
|
break
|
|
}
|
|
} else {
|
|
setCookie("UI_theme", "Light");
|
|
}
|
|
});
|
|
|
|
// -----------------------------------------------------------
|
|
// Restart Backend Python Server
|
|
|
|
function askRestartBackend() {
|
|
// Ask
|
|
showModalWarning(getString('Maint_RestartServer'), getString('Maint_Restart_Server_noti_text'),
|
|
getString('Gen_Cancel'), getString('Maint_RestartServer'), 'restartBackend');
|
|
}
|
|
|
|
// -----------------------------------------------------------
|
|
function restartBackend() {
|
|
|
|
modalEventStatusId = 'modal-message-front-event'
|
|
|
|
const { token: apiToken, apiBase: apiBaseUrl, authHeader } = getAuthContext();
|
|
const url = `${apiBaseUrl}/logs/add-to-execution-queue`;
|
|
|
|
// Execute
|
|
$.ajax({
|
|
method: "POST",
|
|
url: url,
|
|
headers: { ...authHeader, "Content-Type": "application/json" },
|
|
data: JSON.stringify({ action: `cron_restart_backend` }),
|
|
success: function(data, textStatus) {
|
|
// showModalOk ('Result', data );
|
|
|
|
// show message
|
|
showModalOk(getString("general_event_title"), `${getString("general_event_description")} <br/> <br/> <code id='${modalEventStatusId}'></code>`);
|
|
|
|
updateModalState()
|
|
|
|
write_notification('[Maintenance] App manually restarted', 'info')
|
|
}
|
|
})
|
|
}
|
|
|
|
// App lifecycle (completedCalls, executeOnce, handleSuccess, clearCache, etc.) moved to app-init.js
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|