Refactor network API calls to use centralized authentication context and improve cache handling

- Removed redundant getApiToken function and replaced its usage with getAuthContext in network-api.js, network-events.js, and network-init.js.
- Updated cache handling in network-events.js and network-init.js to use CACHE_KEYS constants for better maintainability.
- Introduced cache.js for centralized cache management functions and constants, including cache initialization and retrieval.
- Added app-init.js for application lifecycle management, including cache orchestration and initialization checks.
- Created app_config.php to securely fetch API token and GraphQL port from configuration.
- Improved error handling and logging throughout the codebase for better debugging and maintenance.
This commit is contained in:
Jokob @NetAlertX
2026-02-26 04:21:29 +00:00
parent 00042ab594
commit 63cef590d6
12 changed files with 915 additions and 682 deletions

View File

@@ -419,7 +419,12 @@ async function renderSmallBoxes() {
const apiToken = getSetting("API_TOKEN");
const apiBaseUrl = getApiBase();
const url = `${apiBaseUrl}/device/${getMac()}?period=${encodeURIComponent(period)}`;
// Ensure period is a string, not an element
let periodValue = period;
if (typeof period === 'object' && period !== null && 'value' in period) {
periodValue = period.value;
}
const url = `${apiBaseUrl}/device/${getMac()}?period=${encodeURIComponent(periodValue)}`;
const response = await fetch(url, {
method: "GET",
@@ -553,20 +558,24 @@ function updateDevicePageName(mac) {
//-----------------------------------------------------------------------------------
// Call renderSmallBoxes, then main
(async () => {
await renderSmallBoxes();
main();
})();
window.onload = function async()
{
mac = getMac()
// initializeTabs();
updateChevrons(mac);
updateDevicePageName(mac);
window.onload = function() {
// Always trigger app-init bootstrap
if (typeof executeOnce === 'function') {
executeOnce();
}
mac = getMac();
// Wait for app initialization (cache populated) before using cached data
callAfterAppInitialized(async () => {
updateDevicePageName(mac);
updateChevrons(mac);
await renderSmallBoxes();
main();
});
}
</script>

View File

@@ -526,13 +526,7 @@
function getVisibleDevicesList()
{
// Read cache (skip cookie expiry check)
devicesList = getCache('devicesListAll_JSON', true);
if (devicesList != '') {
devicesList = JSON.parse (devicesList);
} else {
devicesList = [];
}
devicesList = parseDeviceCache(getCache('devicesListAll_JSON', true));
// only loop thru the filtered down list
visibleDevices = getCache("ntx_visible_macs")

281
front/js/app-init.js Normal file
View File

@@ -0,0 +1,281 @@
/* -----------------------------------------------------------------------------
* NetAlertX
* Open Source Network Guard / WIFI & LAN intrusion detector
*
* app-init.js - Front module. Application lifecycle: initialization,
* cache orchestration, and startup sequencing.
* Loaded AFTER common.js — depends on showSpinner(), isEmpty(),
* mergeUniqueArrays(), getSetting(), getString(), getCache(),
* setCache(), and all cache* functions from cache.js.
*-------------------------------------------------------------------------------
# jokob@duck.com GNU GPLv3
----------------------------------------------------------------------------- */
// -----------------------------------------------------------------------------
// initialize
// -----------------------------------------------------------------------------
var completedCalls = []
var completedCalls_final = ['cacheApiConfig', 'cacheSettings', 'cacheStrings', 'cacheDevices'];
var lang_completedCalls = 0;
// -----------------------------------------------------------------------------
// Clearing all the caches
function clearCache() {
showSpinner();
sessionStorage.clear();
localStorage.clear();
// Wait for spinner to show and cache to clear, then reload
setTimeout(() => {
console.warn("clearCache called");
window.location.reload();
}, 100);
}
// ===================================================================
// DEPRECATED: checkSettingChanges() - Replaced by SSE-based manager
// Settings changes are now handled via SSE events
// Kept for backward compatibility, will be removed in future version
// ===================================================================
function checkSettingChanges() {
// SSE manager handles settings_changed events now
if (typeof netAlertXStateManager !== 'undefined' && netAlertXStateManager.initialized) {
return; // SSE handles this now
}
// Fallback for backward compatibility
$.get('php/server/query_json.php', { file: 'app_state.json', nocache: Date.now() }, function(appState) {
const importedMilliseconds = parseInt(appState["settingsImported"] * 1000);
const lastReloaded = parseInt(getCache(CACHE_KEYS.INIT_TIMESTAMP));
if (importedMilliseconds > lastReloaded) {
console.log("Cache needs to be refreshed because of setting changes");
setTimeout(() => {
clearCache();
}, 500);
}
});
}
// ===================================================================
// Display spinner and reload page if not yet initialized
async function handleFirstLoad(callback) {
if (!isAppInitialized()) {
await new Promise(resolve => setTimeout(resolve, 1000));
callback();
}
}
// ===================================================================
// Execute callback once the app is initialized and GraphQL server is running
async function callAfterAppInitialized(callback) {
if (!isAppInitialized() || !(await isGraphQLServerRunning())) {
setTimeout(() => {
callAfterAppInitialized(callback);
}, 500);
} else {
callback();
}
}
// ===================================================================
// Polling function to repeatedly check if the server is running
async function waitForGraphQLServer() {
const pollInterval = 2000; // 2 seconds between each check
let serverRunning = false;
while (!serverRunning) {
serverRunning = await isGraphQLServerRunning();
if (!serverRunning) {
console.log("GraphQL server not running, retrying in 2 seconds...");
await new Promise(resolve => setTimeout(resolve, pollInterval));
}
}
console.log("GraphQL server is now running.");
}
// -----------------------------------------------------------------------------
// Returns 1 if running, 0 otherwise
async function isGraphQLServerRunning() {
try {
const response = await $.get('php/server/query_json.php', { file: 'app_state.json', nocache: Date.now()});
console.log("graphQLServerStarted: " + response["graphQLServerStarted"]);
setCache(CACHE_KEYS.GRAPHQL_STARTED, response["graphQLServerStarted"]);
return response["graphQLServerStarted"];
} catch (error) {
console.error("Failed to check GraphQL server status:", error);
return false;
}
}
// Throttle isAppInitialized logging so the console isn't spammed on every poll.
let _isAppInit_lastLogTime = 0;
function _isAppInitLog(msg) {
const now = Date.now();
if (now - _isAppInit_lastLogTime > 5000) { // log at most once per 5s
console.log(msg);
_isAppInit_lastLogTime = now;
}
}
// -----------------------------------------------------------------------------
// Check if the code has been executed before by checking localStorage
function isAppInitialized() {
lang_shouldBeCompletedCalls = getLangCode() == 'en_us' ? 1 : 2;
// check if each ajax call completed succesfully
for (const call_name of completedCalls_final) {
if (getCache(CACHE_KEYS.initFlag(call_name)) != "true") {
_isAppInitLog(`[isAppInitialized] waiting on ${call_name} (value: ${getCache(CACHE_KEYS.initFlag(call_name))})`);
return false;
}
}
// check if all required languages chached
if(parseInt(getCache(CACHE_KEYS.STRINGS_COUNT)) != lang_shouldBeCompletedCalls)
{
_isAppInitLog(`[isAppInitialized] waiting on cacheStrings: ${getCache(CACHE_KEYS.STRINGS_COUNT)} of ${lang_shouldBeCompletedCalls}`);
return false;
}
return true;
}
// Retry a single async init step up to maxAttempts times with a delay.
async function retryStep(name, fn, maxAttempts = 3, delayMs = 1500) {
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
try {
await fn();
return; // success
} catch (err) {
console.warn(`[executeOnce] ${name} failed (attempt ${attempt}/${maxAttempts}):`, err);
if (attempt < maxAttempts) {
await new Promise(r => setTimeout(r, delayMs));
} else {
console.error(`[executeOnce] ${name} permanently failed after ${maxAttempts} attempts.`);
}
}
}
}
// -----------------------------------------------------------------------------
// Main execution logic
let _executeOnceRunning = false;
async function executeOnce() {
if (_executeOnceRunning) {
console.log('[executeOnce] Already running — skipping duplicate call.');
return;
}
_executeOnceRunning = true;
showSpinner();
// Auto-bust stale cache if code version has changed since last init.
// Clears localStorage in-place so the subsequent init runs fresh without
// requiring a page reload.
if (getCache(CACHE_KEYS.CACHE_VERSION) !== NAX_CACHE_VERSION) {
console.log(`[executeOnce] Cache version mismatch (stored: "${getCache(CACHE_KEYS.CACHE_VERSION)}", expected: "${NAX_CACHE_VERSION}"). Clearing cache.`);
localStorage.clear();
sessionStorage.clear();
}
if (!isAppInitialized()) {
try {
await waitForGraphQLServer(); // Wait for the server to start
await retryStep('cacheApiConfig', cacheApiConfig); // Bootstrap: API_TOKEN + GRAPHQL_PORT from app.conf
await retryStep('cacheDevices', cacheDevices);
await retryStep('cacheSettings', cacheSettings);
await retryStep('cacheStrings', cacheStrings);
console.log("All AJAX callbacks have completed");
onAllCallsComplete();
} finally {
_executeOnceRunning = false;
}
} else {
_executeOnceRunning = false;
}
}
// -----------------------------------------------------------------------------
// Function to handle successful completion of an AJAX call
const handleSuccess = (callName) => {
console.log(`AJAX call successful: ${callName}`);
if(callName.includes("cacheStrings"))
{
completed_tmp = getCache(CACHE_KEYS.STRINGS_COUNT);
completed_tmp == "" ? completed_tmp = 0 : completed_tmp = completed_tmp;
completed_tmp++;
setCache(CACHE_KEYS.STRINGS_COUNT, completed_tmp);
}
setCache(CACHE_KEYS.initFlag(callName), true)
};
// -----------------------------------------------------------------------------
// Function to handle failure of an AJAX call
const handleFailure = (callName, callback) => {
msg = `AJAX call ${callName} failed`
console.error(msg);
if (typeof callback === 'function') {
callback(new Error(msg));
}
};
// -----------------------------------------------------------------------------
// Function to execute when all AJAX calls have completed
const onAllCallsComplete = () => {
completedCalls = mergeUniqueArrays(getCache(CACHE_KEYS.COMPLETED_CALLS).split(','), completedCalls);
setCache(CACHE_KEYS.COMPLETED_CALLS, completedCalls);
// Check if all necessary strings are initialized
if (areAllStringsInitialized()) {
const millisecondsNow = Date.now();
setCache(CACHE_KEYS.INIT_TIMESTAMP, millisecondsNow);
setCache(CACHE_KEYS.CACHE_VERSION, NAX_CACHE_VERSION);
console.log('✔ Cache initialized');
} else {
// If not all strings are initialized, retry initialization
console.log('❌ Not all strings are initialized. Retrying...');
executeOnce();
return;
}
// Call any other initialization functions here if needed
};
// Function to check if all necessary strings are initialized
const areAllStringsInitialized = () => {
// Implement logic to check if all necessary strings are initialized
// Return true if all strings are initialized, false otherwise
return getString('UI_LANG_name') != ""
};
// Call the function to execute the code
executeOnce();
// Set timer for regular UI refresh if enabled
setTimeout(() => {
// page refresh if configured
const refreshTime = getSetting("UI_REFRESH");
if (refreshTime && refreshTime !== "0" && refreshTime !== "") {
console.log("Refreshing page becasue UI_REFRESH setting enabled.");
newTimerRefreshData(clearCache, parseInt(refreshTime)*1000);
}
// Check if page needs to refresh due to setting changes
checkSettingChanges()
}, 10000);
console.log("init app-init.js");

538
front/js/cache.js Normal file
View File

@@ -0,0 +1,538 @@
/* -----------------------------------------------------------------------------
* NetAlertX
* Open Source Network Guard / WIFI & LAN intrusion detector
*
* cache.js - Front module. Cache primitives, settings, strings, and device
* data caching. Loaded FIRST — no dependencies on other NAX files.
* All cross-file calls (handleSuccess, showSpinner, etc.) are
* call-time dependencies resolved after page load.
*-------------------------------------------------------------------------------
# jokob@duck.com GNU GPLv3
----------------------------------------------------------------------------- */
// Cache version stamp — injected by header.php from the app's .VERSION file.
// Changes automatically on every release, busting stale localStorage caches.
// Falls back to a build-time constant so local dev without PHP still works.
const NAX_CACHE_VERSION = (typeof window.NAX_APP_VERSION !== 'undefined')
? window.NAX_APP_VERSION
: 'dev';
// -----------------------------------------------------------------------------
// Central registry of all localStorage cache keys.
// Use these constants (and the helper functions for dynamic keys) everywhere
// instead of bare string literals to prevent silent typo bugs.
// -----------------------------------------------------------------------------
const CACHE_KEYS = {
// --- Init flags (dynamic) ---
// Stores "true" when an AJAX init call completes. Use initFlag(name) below.
initFlag: (name) => `${name}_completed`,
// --- Settings ---
// Stores the value of a setting by its setKey. nax_set_<setKey>
setting: (key) => `nax_set_${key}`,
// Stores the resolved options array for a setting. nax_set_opt_<setKey>
settingOpts: (key) => `nax_set_opt_${key}`,
// --- Language strings ---
// Stores a translated string. pia_lang_<key>_<langCode>
langString: (key, langCode) => `pia_lang_${key}_${langCode}`,
LANG_FALLBACK: 'en_us', // fallback language code
// --- Devices ---
DEVICES_ALL: 'devicesListAll_JSON', // full device list from table_devices.json
DEVICES_TOPOLOGY: 'devicesListNew', // filtered/sorted list for network topology
// --- UI state ---
VISIBLE_MACS: 'ntx_visible_macs', // comma-separated MACs visible in current view
SHOW_ARCHIVED: 'showArchived', // topology show-archived toggle (network page)
SHOW_OFFLINE: 'showOffline', // topology show-offline toggle (network page)
// --- Internal init tracking ---
GRAPHQL_STARTED: 'graphQLServerStarted', // set when GraphQL server responds
STRINGS_COUNT: 'cacheStringsCountCompleted', // count of language packs loaded
COMPLETED_CALLS: 'completedCalls', // comma-joined list of completed init calls
INIT_TIMESTAMP: 'nax_init_timestamp', // ms timestamp of last successful cache init
CACHE_VERSION: 'nax_cache_version', // version stamp for auto-bust on deploy
};
// -----------------------------------------------------------------------------
// localStorage cache helpers
// -----------------------------------------------------------------------------
function getCache(key)
{
// check cache
cachedValue = localStorage.getItem(key)
if(cachedValue)
{
return cachedValue;
}
return "";
}
// -----------------------------------------------------------------------------
function setCache(key, data)
{
localStorage.setItem(key, data);
}
// -----------------------------------------------------------------------------
// Fetch data from a server-generated JSON file via query_json.php.
// Returns a Promise resolving with the "data" array from the response.
// -----------------------------------------------------------------------------
function fetchJson(file) {
return new Promise((resolve, reject) => {
$.get('php/server/query_json.php', { file: file, nocache: Date.now() })
.done((res) => resolve(res['data'] || []))
.fail((err) => reject(err));
});
}
// -----------------------------------------------------------------------------
// Safely parse and normalize device cache data.
// Handles both direct array format and { data: [...] } format.
// Returns an array, or empty array on failure.
function parseDeviceCache(cachedStr) {
if (!cachedStr || cachedStr === "") {
return [];
}
let parsed;
try {
parsed = JSON.parse(cachedStr);
} catch (err) {
console.error('[parseDeviceCache] Failed to parse:', err);
return [];
}
// If result is an object with a .data property, extract it (handles legacy format)
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed) && Array.isArray(parsed.data)) {
console.warn('[parseDeviceCache] Extracting .data property from wrapper object');
parsed = parsed.data;
}
// Ensure result is an array
if (!Array.isArray(parsed)) {
console.error('[parseDeviceCache] Result is not an array:', parsed);
return [];
}
return parsed;
}
// -----------------------------------------------------------------------------
// Returns the API token, base URL, and a ready-to-use Authorization header
// object for all backend API calls. Centralises the repeated
// getSetting("API_TOKEN") + getApiBase() pattern.
// -----------------------------------------------------------------------------
function getAuthContext() {
const token = getSetting('API_TOKEN');
const apiBase = getApiBase();
return {
token,
apiBase,
authHeader: { 'Authorization': 'Bearer ' + token },
};
}
// -----------------------------------------------------------------------------
// Get settings from the .json file generated by the python backend
// and cache them, if available, with options
// -----------------------------------------------------------------------------
// -----------------------------------------------------------------------------
// Bootstrap: fetch API_TOKEN and GRAPHQL_PORT directly from app.conf via the
// PHP helper endpoint. Runs before cacheSettings so that API calls made during
// or after init always have a token available — even if table_settings.json
// hasn't been generated yet. Writes values into the setting() namespace so
// getSetting("API_TOKEN") and getSetting("GRAPHQL_PORT") work immediately.
// -----------------------------------------------------------------------------
function cacheApiConfig() {
return new Promise((resolve, reject) => {
if (getCache(CACHE_KEYS.initFlag('cacheApiConfig')) === 'true') {
resolve();
return;
}
$.get('php/server/app_config.php', { nocache: Date.now() })
.done((res) => {
if (res && res.api_token) {
setCache(CACHE_KEYS.setting('API_TOKEN'), res.api_token);
setCache(CACHE_KEYS.setting('GRAPHQL_PORT'), String(res.graphql_port || 20212));
handleSuccess('cacheApiConfig');
resolve();
} else {
console.warn('[cacheApiConfig] Response missing api_token — will rely on cacheSettings fallback');
resolve(); // non-fatal: cacheSettings will still populate these
}
})
.fail((err) => {
console.warn('[cacheApiConfig] Failed to reach app_config.php:', err);
resolve(); // non-fatal fallback
});
});
}
function cacheSettings()
{
return new Promise((resolve, reject) => {
if(getCache(CACHE_KEYS.initFlag('cacheSettings')) === "true")
{
resolve();
return;
}
// plugins.json may not exist on first boot — treat its absence as non-fatal
Promise.all([fetchJson('table_settings.json'), fetchJson('plugins.json').catch(() => [])])
.then(([settingsArr, pluginsArr]) => {
pluginsData = pluginsArr;
settingsData = settingsArr;
// Defensive: Accept either array or object with .data property
// for both settings and plugins
if (!Array.isArray(settingsData)) {
if (settingsData && Array.isArray(settingsData.data)) {
settingsData = settingsData.data;
} else {
console.error('[cacheSettings] settingsData is not an array:', settingsData);
reject(new Error('settingsData is not an array'));
return;
}
}
// Normalize plugins array too (may have { data: [...] } format)
if (!Array.isArray(pluginsData)) {
if (pluginsData && Array.isArray(pluginsData.data)) {
pluginsData = pluginsData.data;
} else {
console.warn('[cacheSettings] pluginsData is not an array, treating as empty');
pluginsData = [];
}
}
settingsData.forEach((set) => {
resolvedOptions = createArray(set.setOptions)
resolvedOptionsOld = resolvedOptions
setPlugObj = {};
options_params = [];
resolved = ""
// proceed only if first option item contains something to resolve
if( !set.setKey.includes("__metadata") &&
resolvedOptions.length != 0 &&
resolvedOptions[0].includes("{value}"))
{
// get setting definition from the plugin config if available
setPlugObj = getPluginSettingObject(pluginsData, set.setKey)
// check if options contains parameters and resolve
if(setPlugObj != {} && setPlugObj["options_params"])
{
// get option_params for {value} resolution
options_params = setPlugObj["options_params"]
if(options_params != [])
{
// handles only strings of length == 1
resolved = resolveParams(options_params, resolvedOptions[0])
if(resolved.includes('"')) // check if list of strings
{
resolvedOptions = `[${resolved}]`
} else // one value only
{
resolvedOptions = `["${resolved}"]`
}
}
}
}
setCache(CACHE_KEYS.setting(set.setKey), set.setValue)
setCache(CACHE_KEYS.settingOpts(set.setKey), resolvedOptions)
});
handleSuccess('cacheSettings');
resolve();
})
.catch((err) => { handleFailure('cacheSettings'); reject(err); });
});
}
// -----------------------------------------------------------------------------
// Get a setting options value by key
function getSettingOptions (key) {
result = getCache(CACHE_KEYS.settingOpts(key));
if (result == "")
{
result = []
}
return result;
}
// -----------------------------------------------------------------------------
// Get a setting value by key
function getSetting (key) {
result = getCache(CACHE_KEYS.setting(key));
return result;
}
// -----------------------------------------------------------------------------
// Get language string
// -----------------------------------------------------------------------------
function cacheStrings() {
return new Promise((resolve, reject) => {
if(getCache(CACHE_KEYS.initFlag('cacheStrings')) === "true")
{
resolve();
return;
}
// Create a promise for each language (include en_us by default as fallback)
languagesToLoad = ['en_us']
additionalLanguage = getLangCode()
if(additionalLanguage != 'en_us')
{
languagesToLoad.push(additionalLanguage)
}
console.log(languagesToLoad);
const languagePromises = languagesToLoad.map((language_code) => {
return new Promise((resolveLang, rejectLang) => {
// Fetch core strings and translations
$.get(`php/templates/language/${language_code}.json?nocache=${Date.now()}`)
.done((res) => {
// Iterate over each key-value pair and store the translations
Object.entries(res).forEach(([key, value]) => {
setCache(CACHE_KEYS.langString(key, language_code), value);
});
// Fetch strings and translations from plugins (non-fatal — file may
// not exist on first boot or immediately after a cache clear)
fetchJson('table_plugins_language_strings.json')
.catch((pluginError) => {
console.warn('[cacheStrings] Plugin language strings unavailable (non-fatal):', pluginError);
return []; // treat as empty list
})
.then((data) => {
// Defensive: ensure data is an array (fetchJson may return
// an object, undefined, or empty string on edge cases)
if (!Array.isArray(data)) { data = []; }
// Store plugin translations
data.forEach((langString) => {
setCache(CACHE_KEYS.langString(langString.String_Key, langString.Language_Code), langString.String_Value);
});
// Handle successful completion of language processing
handleSuccess('cacheStrings');
resolveLang();
});
})
.fail((error) => {
// Handle failure in core strings fetching
rejectLang(error);
});
});
});
// Wait for all language promises to complete
Promise.all(languagePromises)
.then(() => {
// All languages processed successfully
resolve();
})
.catch((error) => {
// Handle failure in any of the language processing
handleFailure('cacheStrings');
reject(error);
});
});
}
// -----------------------------------------------------------------------------
// Get translated language string
function getString(key) {
function fetchString(key) {
lang_code = getLangCode();
let result = getCache(CACHE_KEYS.langString(key, lang_code));
if (isEmpty(result)) {
result = getCache(CACHE_KEYS.langString(key, CACHE_KEYS.LANG_FALLBACK));
}
return result;
}
if (isAppInitialized()) {
return fetchString(key);
} else {
callAfterAppInitialized(() => fetchString(key));
}
}
// -----------------------------------------------------------------------------
// Get current language ISO code
// 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");
let lang_code = 'en_us';
switch (UI_LANG) {
case 'English (en_us)':
lang_code = 'en_us';
break;
case 'Spanish (es_es)':
lang_code = 'es_es';
break;
case 'German (de_de)':
lang_code = 'de_de';
break;
case 'Farsi (fa_fa)':
lang_code = 'fa_fa';
break;
case 'French (fr_fr)':
lang_code = 'fr_fr';
break;
case 'Norwegian (nb_no)':
lang_code = 'nb_no';
break;
case 'Polish (pl_pl)':
lang_code = 'pl_pl';
break;
case 'Portuguese (pt_br)':
lang_code = 'pt_br';
break;
case 'Portuguese (pt_pt)':
lang_code = 'pt_pt';
break;
case 'Turkish (tr_tr)':
lang_code = 'tr_tr';
break;
case 'Swedish (sv_sv)':
lang_code = 'sv_sv';
break;
case 'Italian (it_it)':
lang_code = 'it_it';
break;
case 'Japanese (ja_jp)':
lang_code = 'ja_jp';
break;
case 'Russian (ru_ru)':
lang_code = 'ru_ru';
break;
case 'Chinese (zh_cn)':
lang_code = 'zh_cn';
break;
case 'Czech (cs_cz)':
lang_code = 'cs_cz';
break;
case 'Arabic (ar_ar)':
lang_code = 'ar_ar';
break;
case 'Catalan (ca_ca)':
lang_code = 'ca_ca';
break;
case 'Ukrainian (uk_uk)':
lang_code = 'uk_ua';
break;
case 'Vietnamese (vi_vn)':
lang_code = 'vi_vn';
break;
}
return lang_code;
}
// -----------------------------------------------------------------------------
// A function to get a device property using the mac address as key and DB column name as parameter
// for the value to be returned
function getDevDataByMac(macAddress, dbColumn) {
const sessionDataKey = CACHE_KEYS.DEVICES_ALL;
const devicesCache = getCache(sessionDataKey);
if (!devicesCache || devicesCache == "") {
console.warn(`[getDevDataByMac] Cache key "${sessionDataKey}" is empty — cache may not be initialized yet.`);
return null;
}
const devices = parseDeviceCache(devicesCache);
if (devices.length === 0) {
return null;
}
for (const device of devices) {
if (device["devMac"].toLowerCase() === macAddress.toLowerCase()) {
if(dbColumn)
{
return device[dbColumn];
}
else
{
return device
}
}
}
console.error("⚠ Device with MAC not found:" + macAddress)
return null; // Return a default value if MAC address is not found
}
// -----------------------------------------------------------------------------
// Cache the devices as one JSON
function cacheDevices()
{
return new Promise((resolve, reject) => {
if(getCache(CACHE_KEYS.initFlag('cacheDevices')) === "true")
{
resolve();
return;
}
fetchJson('table_devices.json')
.then((arr) => {
devicesListAll_JSON = arr;
devicesListAll_JSON_str = JSON.stringify(devicesListAll_JSON)
if(devicesListAll_JSON_str == "")
{
showSpinner()
setTimeout(() => {
cacheDevices()
}, 1000);
}
setCache(CACHE_KEYS.DEVICES_ALL, devicesListAll_JSON_str)
handleSuccess('cacheDevices');
resolve();
})
.catch((err) => { handleFailure('cacheDevices'); reject(err); });
}
);
}
var devicesListAll_JSON = []; // this will contain a list off all devices

View File

@@ -19,40 +19,10 @@ const allLanguages = ["ar_ar","ca_ca","cs_cz","de_de",
"tr_tr","uk_ua","vi_vn","zh_cn"]; // needs to be same as in lang.php
var settingsJSON = {}
// NAX_CACHE_VERSION and CACHE_KEYS moved to cache.js
// -----------------------------------------------------------------------------
// Simple session cache withe expiration managed via cookies
// -----------------------------------------------------------------------------
function getCache(key, noCookie = false)
{
// check cache
cachedValue = localStorage.getItem(key)
// console.log(cachedValue);
if(cachedValue)
{
// // check if not expired
// if(noCookie || getCookie(key + '_session_expiry') != "")
// {
return cachedValue;
// }
}
return "";
}
// -----------------------------------------------------------------------------
function setCache(key, data, expirationMinutes='')
{
localStorage.setItem(key, data);
// // create cookie if expiration set to handle refresh of data
// if (expirationMinutes != '')
// {
// setCookie ('cache_session_expiry', 'OK', 1)
// }
}
// getCache, setCache, fetchJson, getAuthContext moved to cache.js
// -----------------------------------------------------------------------------
@@ -93,288 +63,13 @@ function deleteCookie (cookie) {
document.cookie = cookie + '=;expires=Thu, 01 Jan 1970 00:00:00 UTC';
}
// -----------------------------------------------------------------------------
function deleteAllCookies() {
// Array of cookies
var allCookies = document.cookie.split(";");
// For each cookie
for (var i = 0; i < allCookies.length; i++) {
var cookie = allCookies[i].trim();
var eqPos = cookie.indexOf("=");
var name = eqPos > -1 ? cookie.substr(0, eqPos) : cookie;
document.cookie = name + "=;expires=Thu, 01 Jan 1970 00:00:00 UTC";
}
}
// cacheApiConfig, cacheSettings, getSettingOptions, getSetting moved to cache.js
// -----------------------------------------------------------------------------
// Get settings from the .json file generated by the python backend
// and cache them, if available, with options
// -----------------------------------------------------------------------------
function cacheSettings()
{
return new Promise((resolve, reject) => {
if(!getCache('cacheSettings_completed') === true)
{
$.get('php/server/query_json.php', { file: 'table_settings.json', nocache: Date.now() }, function(resSet) {
$.get('php/server/query_json.php', { file: 'plugins.json', nocache: Date.now() }, function(resPlug) {
pluginsData = resPlug["data"];
settingsData = resSet["data"];
settingsData.forEach((set) => {
resolvedOptions = createArray(set.setOptions)
resolvedOptionsOld = resolvedOptions
setPlugObj = {};
options_params = [];
resolved = ""
// proceed only if first option item contains something to resolve
if( !set.setKey.includes("__metadata") &&
resolvedOptions.length != 0 &&
resolvedOptions[0].includes("{value}"))
{
// get setting definition from the plugin config if available
setPlugObj = getPluginSettingObject(pluginsData, set.setKey)
// check if options contains parameters and resolve
if(setPlugObj != {} && setPlugObj["options_params"])
{
// get option_params for {value} resolution
options_params = setPlugObj["options_params"]
if(options_params != [])
{
// handles only strings of length == 1
resolved = resolveParams(options_params, resolvedOptions[0])
if(resolved.includes('"')) // check if list of strings
{
resolvedOptions = `[${resolved}]`
} else // one value only
{
resolvedOptions = `["${resolved}"]`
}
}
}
}
setCache(`nax_set_${set.setKey}`, set.setValue)
setCache(`nax_set_opt_${set.setKey}`, resolvedOptions)
});
}).then(() => handleSuccess('cacheSettings', resolve())).catch(() => handleFailure('cacheSettings', reject("cacheSettings already completed"))); // handle AJAX synchronization
})
}
});
}
// -----------------------------------------------------------------------------
// Get a setting options value by key
function getSettingOptions (key) {
// handle initial load to make sure everything is set-up and cached
// handleFirstLoad()
result = getCache(`nax_set_opt_${key}`, true);
if (result == "")
{
// console.log(`Setting options with key "${key}" not found`)
result = []
}
return result;
}
// -----------------------------------------------------------------------------
// Get a setting value by key
function getSetting (key) {
// handle initial load to make sure everything is set-up and cached
// handleFirstLoad()
result = getCache(`nax_set_${key}`, true);
// if (result == "")
// {
// console.log(`Setting with key "${key}" not found`)
// }
return result;
}
// -----------------------------------------------------------------------------
// Get language string
// -----------------------------------------------------------------------------
function cacheStrings() {
return new Promise((resolve, reject) => {
// Create a promise for each language (include en_us by default as fallback)
languagesToLoad = ['en_us']
additionalLanguage = getLangCode()
if(additionalLanguage != 'en_us')
{
languagesToLoad.push(additionalLanguage)
}
console.log(languagesToLoad);
const languagePromises = languagesToLoad.map((language_code) => {
return new Promise((resolveLang, rejectLang) => {
// Fetch core strings and translations
$.get(`php/templates/language/${language_code}.json?nocache=${Date.now()}`)
.done((res) => {
// Iterate over each key-value pair and store the translations
Object.entries(res).forEach(([key, value]) => {
setCache(`pia_lang_${key}_${language_code}`, value);
});
// Fetch strings and translations from plugins
$.get('php/server/query_json.php', { file: 'table_plugins_language_strings.json', nocache: Date.now() })
.done((pluginRes) => {
const data = pluginRes["data"];
// Store plugin translations
data.forEach((langString) => {
setCache(`pia_lang_${langString.String_Key}_${langString.Language_Code}`, langString.String_Value);
});
// Handle successful completion of language processing
handleSuccess(`cacheStrings`, resolveLang);
})
.fail((pluginError) => {
// Handle failure in plugin strings fetching
rejectLang(pluginError);
});
})
.fail((error) => {
// Handle failure in core strings fetching
rejectLang(error);
});
});
});
// Wait for all language promises to complete
Promise.all(languagePromises)
.then(() => {
// All languages processed successfully
resolve();
})
.catch((error) => {
// Handle failure in any of the language processing
handleFailure('cacheStrings', reject);
});
});
}
// -----------------------------------------------------------------------------
// Get translated language string
function getString(key) {
function fetchString(key) {
lang_code = getLangCode();
let result = getCache(`pia_lang_${key}_${lang_code}`, true);
if (isEmpty(result)) {
result = getCache(`pia_lang_${key}_en_us`, true);
}
return result;
}
if (isAppInitialized()) {
return fetchString(key);
} else {
callAfterAppInitialized(() => fetchString(key));
}
}
// -----------------------------------------------------------------------------
// Get current language ISO code
// 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");
let lang_code = 'en_us';
switch (UI_LANG) {
case 'English (en_us)':
lang_code = 'en_us';
break;
case 'Spanish (es_es)':
lang_code = 'es_es';
break;
case 'German (de_de)':
lang_code = 'de_de';
break;
case 'Farsi (fa_fa)':
lang_code = 'fa_fa';
break;
case 'French (fr_fr)':
lang_code = 'fr_fr';
break;
case 'Norwegian (nb_no)':
lang_code = 'nb_no';
break;
case 'Polish (pl_pl)':
lang_code = 'pl_pl';
break;
case 'Portuguese (pt_br)':
lang_code = 'pt_br';
break;
case 'Portuguese (pt_pt)':
lang_code = 'pt_pt';
break;
case 'Turkish (tr_tr)':
lang_code = 'tr_tr';
break;
case 'Swedish (sv_sv)':
lang_code = 'sv_sv';
break;
case 'Italian (it_it)':
lang_code = 'it_it';
break;
case 'Japanese (ja_jp)':
lang_code = 'ja_jp';
break;
case 'Russian (ru_ru)':
lang_code = 'ru_ru';
break;
case 'Chinese (zh_cn)':
lang_code = 'zh_cn';
break;
case 'Czech (cs_cz)':
lang_code = 'cs_cz';
break;
case 'Arabic (ar_ar)':
lang_code = 'ar_ar';
break;
case 'Catalan (ca_ca)':
lang_code = 'ca_ca';
break;
case 'Ukrainian (uk_uk)':
lang_code = 'uk_ua';
break;
case 'Vietnamese (vi_vn)':
lang_code = 'vi_vn';
break;
}
return lang_code;
}
// cacheStrings, getString, getLangCode moved to cache.js
const tz = getSetting("TIMEZONE") || 'Europe/Berlin';
const LOCALE = getSetting('UI_LOCALE') || 'en-GB';
@@ -718,14 +413,13 @@ function numberArrayFromString(data)
// -----------------------------------------------------------------------------
// Update network parent/child relationship (network tree)
function updateNetworkLeaf(leafMac, parentMac) {
const apiBase = getApiBase();
const apiToken = getSetting("API_TOKEN");
const { apiBase, authHeader } = getAuthContext();
const url = `${apiBase}/device/${leafMac}/update-column`;
$.ajax({
method: "POST",
url: url,
headers: { "Authorization": `Bearer ${apiToken}` },
headers: authHeader,
data: JSON.stringify({ columnName: "devParentMAC", columnValue: parentMac }),
contentType: "application/json",
success: function(response) {
@@ -1157,72 +851,7 @@ function isRandomMAC(mac)
return options;
}
// -----------------------------------------------------------------------------
// A function to get a device property using the mac address as key and DB column nakme as parameter
// for the value to be returned
function getDevDataByMac(macAddress, dbColumn) {
const sessionDataKey = 'devicesListAll_JSON';
const devicesCache = getCache(sessionDataKey);
if (!devicesCache || devicesCache == "") {
console.error(`Session variable "${sessionDataKey}" not found.`);
return null;
}
const devices = JSON.parse(devicesCache);
for (const device of devices) {
if (device["devMac"].toLowerCase() === macAddress.toLowerCase()) {
if(dbColumn)
{
return device[dbColumn];
}
else
{
return device
}
}
}
console.error("⚠ Device with MAC not found:" + macAddress)
return null; // Return a default value if MAC address is not found
}
// -----------------------------------------------------------------------------
// Cache the devices as one JSON
function cacheDevices()
{
return new Promise((resolve, reject) => {
$.get('php/server/query_json.php', { file: 'table_devices.json', nocache: Date.now() }, function(data) {
// console.log(data)
devicesListAll_JSON = data["data"]
devicesListAll_JSON_str = JSON.stringify(devicesListAll_JSON)
if(devicesListAll_JSON_str == "")
{
showSpinner()
setTimeout(() => {
cacheDevices()
}, 1000);
}
// console.log(devicesListAll_JSON_str);
setCache('devicesListAll_JSON', devicesListAll_JSON_str)
// console.log(getCache('devicesListAll_JSON'))
}).then(() => handleSuccess('cacheDevices', resolve())).catch(() => handleFailure('cacheDevices', reject("cacheDevices already completed"))); // handle AJAX synchronization
}
);
}
var devicesListAll_JSON = []; // this will contain a list off all devices
// getDevDataByMac, cacheDevices, devicesListAll_JSON moved to cache.js
// -----------------------------------------------------------------------------
function isEmpty(value)
@@ -1369,18 +998,13 @@ function updateApi(apiEndpoints)
// value has to be in format event|param. e.g. run|ARPSCAN
action = `${getGuid()}|update_api|${apiEndpoints}`
// Get data from the server
const apiToken = getSetting("API_TOKEN");
const apiBaseUrl = getApiBase();
const { token: apiToken, apiBase: apiBaseUrl, authHeader } = getAuthContext();
const url = `${apiBaseUrl}/logs/add-to-execution-queue`;
$.ajax({
method: "POST",
url: url,
headers: {
"Authorization": "Bearer " + apiToken,
"Content-Type": "application/json"
},
headers: { ...authHeader, "Content-Type": "application/json" },
data: JSON.stringify({ action: action }),
success: function(data, textStatus) {
console.log(data)
@@ -1548,16 +1172,11 @@ function hideUIelements(setKey) {
function getDevicesList()
{
// Read cache (skip cookie expiry check)
devicesList = getCache('devicesListAll_JSON', true);
if (devicesList != '') {
devicesList = JSON.parse (devicesList);
} else {
devicesList = [];
}
const cached = getCache(CACHE_KEYS.DEVICES_ALL);
let devicesList = parseDeviceCache(cached);
// only loop thru the filtered down list
visibleDevices = getCache("ntx_visible_macs")
visibleDevices = getCache(CACHE_KEYS.VISIBLE_MACS)
if(visibleDevices != "") {
visibleDevicesMACs = visibleDevices.split(',');
@@ -1616,18 +1235,14 @@ function restartBackend() {
modalEventStatusId = 'modal-message-front-event'
const apiToken = getSetting("API_TOKEN");
const apiBaseUrl = getApiBase();
const { token: apiToken, apiBase: apiBaseUrl, authHeader } = getAuthContext();
const url = `${apiBaseUrl}/logs/add-to-execution-queue`;
// Execute
$.ajax({
method: "POST",
url: url,
headers: {
"Authorization": "Bearer " + apiToken,
"Content-Type": "application/json"
},
headers: { ...authHeader, "Content-Type": "application/json" },
data: JSON.stringify({ action: `cron_restart_backend` }),
success: function(data, textStatus) {
// showModalOk ('Result', data );
@@ -1642,237 +1257,8 @@ function restartBackend() {
})
}
// -----------------------------------------------------------------------------
// initialize
// -----------------------------------------------------------------------------
// -----------------------------------------------------------------------------
// Define a unique key for storing the flag in sessionStorage
const sessionStorageKey = "myScriptExecuted_common_js";
var completedCalls = []
var completedCalls_final = ['cacheSettings', 'cacheStrings', 'cacheDevices'];
var lang_completedCalls = 0;
// -----------------------------------------------------------------------------
// Clearing all the caches
function clearCache() {
showSpinner();
sessionStorage.clear();
localStorage.clear();
setTimeout(() => {
console.warn("clearChache called");
window.location.reload();
}, 500);
}
// ===================================================================
// DEPRECATED: checkSettingChanges() - Replaced by SSE-based manager
// Settings changes are now handled via SSE events
// Kept for backward compatibility, will be removed in future version
// ===================================================================
function checkSettingChanges() {
// SSE manager handles settings_changed events now
if (typeof netAlertXStateManager !== 'undefined' && netAlertXStateManager.initialized) {
return; // SSE handles this now
}
// Fallback for backward compatibility
$.get('php/server/query_json.php', { file: 'app_state.json', nocache: Date.now() }, function(appState) {
const importedMilliseconds = parseInt(appState["settingsImported"] * 1000);
const lastReloaded = parseInt(sessionStorage.getItem(sessionStorageKey + '_time'));
if (importedMilliseconds > lastReloaded) {
console.log("Cache needs to be refreshed because of setting changes");
setTimeout(() => {
clearCache();
}, 500);
}
});
}
// ===================================================================
// Display spinner and reload page if not yet initialized
async function handleFirstLoad(callback) {
if (!isAppInitialized()) {
await new Promise(resolve => setTimeout(resolve, 1000));
callback();
}
}
// ===================================================================
// Execute callback once the app is initialized and GraphQL server is running
async function callAfterAppInitialized(callback) {
if (!isAppInitialized() || !(await isGraphQLServerRunning())) {
setTimeout(() => {
callAfterAppInitialized(callback);
}, 500);
} else {
callback();
}
}
// ===================================================================
// Polling function to repeatedly check if the server is running
async function waitForGraphQLServer() {
const pollInterval = 2000; // 2 seconds between each check
let serverRunning = false;
while (!serverRunning) {
serverRunning = await isGraphQLServerRunning();
if (!serverRunning) {
console.log("GraphQL server not running, retrying in 2 seconds...");
await new Promise(resolve => setTimeout(resolve, pollInterval));
}
}
console.log("GraphQL server is now running.");
}
// -----------------------------------------------------------------------------
// Returns 1 if running, 0 otherwise
async function isGraphQLServerRunning() {
try {
const response = await $.get('php/server/query_json.php', { file: 'app_state.json', nocache: Date.now()});
console.log("graphQLServerStarted: " + response["graphQLServerStarted"]);
setCache("graphQLServerStarted", response["graphQLServerStarted"]);
return response["graphQLServerStarted"];
} catch (error) {
console.error("Failed to check GraphQL server status:", error);
return false;
}
}
// -----------------------------------------------------------------------------
// Check if the code has been executed before by checking sessionStorage
function isAppInitialized() {
lang_shouldBeCompletedCalls = getLangCode() == 'en_us' ? 1 : 2;
// check if each ajax call completed succesfully
$.each(completedCalls_final, function(index, call_name){
if(getCache(call_name + "_completed") != "true")
{
console.log(`[isAppInitialized] AJAX call ${call_name} unsuccesful: ${getCache(call_name + "_completed")}`)
return false;
}
});
// check if all required languages chached
if(parseInt(getCache("cacheStringsCountCompleted")) != lang_shouldBeCompletedCalls)
{
console.log(`[isAppInitialized] AJAX call cacheStrings unsuccesful: ${getCache("cacheStringsCountCompleted")} out of ${lang_shouldBeCompletedCalls}`)
return false;
}
return true;
}
// -----------------------------------------------------------------------------
// Main execution logic
async function executeOnce() {
showSpinner();
if (!isAppInitialized()) {
try {
await waitForGraphQLServer(); // Wait for the server to start
await cacheDevices();
await cacheSettings();
await cacheStrings();
console.log("All AJAX callbacks have completed");
onAllCallsComplete();
} catch (error) {
console.error("Error:", error);
}
}
}
// -----------------------------------------------------------------------------
// Function to handle successful completion of an AJAX call
const handleSuccess = (callName) => {
console.log(`AJAX call successful: ${callName}`);
if(callName.includes("cacheStrings"))
{
completed_tmp = getCache("cacheStringsCountCompleted");
completed_tmp == "" ? completed_tmp = 0 : completed_tmp = completed_tmp;
completed_tmp++;
setCache("cacheStringsCountCompleted", completed_tmp);
}
setCache(callName + "_completed", true)
};
// -----------------------------------------------------------------------------
// Function to handle failure of an AJAX call
const handleFailure = (callName, callback) => {
msg = `AJAX call ${callName} failed`
console.error(msg);
// Implement retry logic here if needed
// write_notification(msg, 'interrupt')
};
// -----------------------------------------------------------------------------
// Function to execute when all AJAX calls have completed
const onAllCallsComplete = () => {
completedCalls = mergeUniqueArrays(getCache('completedCalls').split(','), completedCalls);
setCache('completedCalls', completedCalls);
// Check if all necessary strings are initialized
if (areAllStringsInitialized()) {
sessionStorage.setItem(sessionStorageKey, "true");
const millisecondsNow = Date.now();
sessionStorage.setItem(sessionStorageKey + '_time', millisecondsNow);
console.log('✔ Cache initialized');
// setTimeout(() => {
// location.reload()
// }, 10);
} else {
// If not all strings are initialized, retry initialization
console.log('❌ Not all strings are initialized. Retrying...');
executeOnce();
return;
}
// Call any other initialization functions here if needed
};
// Function to check if all necessary strings are initialized
const areAllStringsInitialized = () => {
// Implement logic to check if all necessary strings are initialized
// Return true if all strings are initialized, false otherwise
return getString('UI_LANG_name') != ""
};
// Call the function to execute the code
executeOnce();
// Set timer for regular UI refresh if enabled
setTimeout(() => {
// page refresh if configured
const refreshTime = getSetting("UI_REFRESH");
if (refreshTime && refreshTime !== "0" && refreshTime !== "") {
console.log("Refreshing page becasue UI_REFRESH setting enabled.");
newTimerRefreshData(clearCache, parseInt(refreshTime)*1000);
}
// Check if page needs to refresh due to setting changes
checkSettingChanges()
}, 10000);
console.log("init common.js");
// App lifecycle (completedCalls, executeOnce, handleSuccess, clearCache, etc.) moved to app-init.js

View File

@@ -1,21 +1,6 @@
// network-api.js
// API calls and data loading functions for network topology
/**
* Get API token, waiting if necessary for settings to load
* @returns {string} The API token
*/
function getApiToken() {
let token = getSetting("API_TOKEN");
// If token is not yet available, log warning
if (!token || token.trim() === '') {
console.warn("API_TOKEN not yet loaded from settings");
}
return token;
}
/**
* Load network nodes (network device types)
* Creates top-level tabs for each network device
@@ -52,8 +37,7 @@ function loadNetworkNodes() {
ORDER BY parent.devName;
`;
const apiBase = getApiBase();
const apiToken = getApiToken();
const { token: apiToken, apiBase, authHeader } = getAuthContext();
// Verify token is available
if (!apiToken || apiToken.trim() === '') {
@@ -66,7 +50,7 @@ function loadNetworkNodes() {
$.ajax({
url,
method: "POST",
headers: { "Authorization": `Bearer ${apiToken}` },
headers: { ...authHeader, "Content-Type": "application/json" },
data: JSON.stringify({ rawSql: btoa(unescape(encodeURIComponent(rawSql))) }),
contentType: "application/json",
success: function(data) {
@@ -95,8 +79,7 @@ function loadNetworkNodes() {
* @param {boolean} options.assignMode - Whether to show assign/unassign buttons
*/
function loadDeviceTable({ sql, containerSelector, tableId, wrapperHtml = null, assignMode = true }) {
const apiBase = getApiBase();
const apiToken = getApiToken();
const { token: apiToken, apiBase, authHeader } = getAuthContext();
// Verify token is available
if (!apiToken || apiToken.trim() === '') {
@@ -109,7 +92,7 @@ function loadDeviceTable({ sql, containerSelector, tableId, wrapperHtml = null,
$.ajax({
url,
method: "POST",
headers: { "Authorization": `Bearer ${apiToken}` },
headers: { ...authHeader, "Content-Type": "application/json" },
data: JSON.stringify({ rawSql: btoa(unescape(encodeURIComponent(sql))) }),
contentType: "application/json",
success: function(data) {

View File

@@ -84,12 +84,12 @@ $(window).on('resize', function () {
*/
$(document).ready(function () {
// Restore cached values on load
const cachedOffline = getCache('showOffline');
const cachedOffline = getCache(CACHE_KEYS.SHOW_OFFLINE);
if (cachedOffline !== null) {
$('input[name="showOffline"]').prop('checked', cachedOffline === 'true');
}
const cachedArchived = getCache('showArchived');
const cachedArchived = getCache(CACHE_KEYS.SHOW_ARCHIVED);
if (cachedArchived !== null) {
$('input[name="showArchived"]').prop('checked', cachedArchived === 'true');
}
@@ -102,7 +102,7 @@ $(document).ready(function () {
if (!isOfflineChecked) {
archivedToggle.prop('checked', false);
archivedToggle.prop('disabled', true);
setCache('showArchived', false);
setCache(CACHE_KEYS.SHOW_ARCHIVED, false);
} else {
archivedToggle.prop('disabled', false);
}
@@ -115,6 +115,8 @@ $(document).ready(function () {
$('input[name="showOffline"], input[name="showArchived"]').on('change', function () {
const name = $(this).attr('name');
const value = $(this).is(':checked');
// setCache(name, value) works because CACHE_KEYS.SHOW_OFFLINE === 'showOffline'
// and CACHE_KEYS.SHOW_ARCHIVED === 'showArchived' — matches the DOM input name attr.
setCache(name, value);
// Update state of showArchived if showOffline changed

View File

@@ -12,8 +12,8 @@ var showOffline = false;
*/
function initNetworkTopology() {
networkDeviceTypes = getSetting("NETWORK_DEVICE_TYPES").replace("[", "").replace("]", "");
showArchived = getCache('showArchived') === "true";
showOffline = getCache('showOffline') === "true";
showArchived = getCache(CACHE_KEYS.SHOW_ARCHIVED) === "true";
showOffline = getCache(CACHE_KEYS.SHOW_OFFLINE) === "true";
console.log('showArchived:', showArchived);
console.log('showOffline:', showOffline);
@@ -33,8 +33,7 @@ function initNetworkTopology() {
FROM Devices a
`;
const apiBase = getApiBase();
const apiToken = getApiToken();
const { token: apiToken, apiBase, authHeader } = getAuthContext();
// Verify token is available before making API call
if (!apiToken || apiToken.trim() === '') {
@@ -51,7 +50,7 @@ function initNetworkTopology() {
$.ajax({
url,
method: "POST",
headers: { "Authorization": `Bearer ${apiToken}` },
headers: { ...authHeader, "Content-Type": "application/json" },
data: JSON.stringify({ rawSql: btoa(unescape(encodeURIComponent(rawSql))) }),
contentType: "application/json",
success: function(data) {
@@ -121,7 +120,7 @@ function initNetworkTopology() {
}
});
setCache('devicesListNew', JSON.stringify(devicesSorted));
setCache(CACHE_KEYS.DEVICES_TOPOLOGY, JSON.stringify(devicesSorted));
deviceListGlobal = devicesSorted;
// Render filtered result

View File

@@ -240,8 +240,8 @@
// Initialize device selectors / pickers fields
function initDeviceSelectors() {
// Parse device list
devicesList = JSON.parse(getCache('devicesListAll_JSON'));
// Parse device list using the shared helper
devicesList = parseDeviceCache(getCache('devicesListAll_JSON'));
// Check if the device list exists and is an array
if (Array.isArray(devicesList)) {

View File

@@ -0,0 +1,38 @@
<?php
// ---- IMPORTS ----
// Check if authenticated - also populates $api_token and $configLines from app.conf
require_once $_SERVER['DOCUMENT_ROOT'] . '/php/templates/security.php';
// getSettingValue() reads from Python-generated table_settings.json (reliable runtime source)
require_once dirname(__FILE__) . '/init.php';
// ---- IMPORTS ----
// Only respond to GET requests
if ($_SERVER['REQUEST_METHOD'] !== 'GET') {
http_response_code(405);
header('Content-Type: application/json');
echo json_encode(['error' => 'Method not allowed']);
exit;
}
// API_TOKEN: security.php extracts it from app.conf but the value is empty until Python
// initialise.py runs. Fall back to table_settings.json (runtime source of truth).
$resolved_token = !empty($api_token) ? $api_token : getSettingValue('API_TOKEN');
// GRAPHQL_PORT: format in app.conf is bare integer — GRAPHQL_PORT=20212 (no quotes)
$graphql_port_raw = getConfigLine('/^GRAPHQL_PORT\s*=/', $configLines);
$graphql_port = isset($graphql_port_raw[1]) ? (int) trim($graphql_port_raw[1]) : 20212;
// Validate we have something useful before returning
if (empty($resolved_token) || str_starts_with($resolved_token, 'Could not')) {
http_response_code(500);
header('Content-Type: application/json');
echo json_encode(['error' => 'Could not read API_TOKEN from configuration']);
exit;
}
header('Content-Type: application/json');
echo json_encode([
'api_token' => $resolved_token,
'graphql_port' => $graphql_port,
]);

View File

@@ -43,7 +43,10 @@
<script src="lib/datatables.net-bs/js/dataTables.bootstrap.min.js"></script>
<script src="lib/datatables.net/js/dataTables.select.min.js"></script>
<script>window.NAX_APP_VERSION = "<?php echo trim(file_exists('/app/.VERSION') ? file_get_contents('/app/.VERSION') : 'dev'); ?>";</script>
<script src="js/cache.js?v=<?php include 'php/templates/version.php'; ?>"></script>
<script src="js/common.js?v=<?php include 'php/templates/version.php'; ?>"></script>
<script src="js/app-init.js?v=<?php include 'php/templates/version.php'; ?>"></script>
<script src="js/sse_manager.js?v=<?php include 'php/templates/version.php'; ?>"></script>
<script src="js/api.js?v=<?php include 'php/templates/version.php'; ?>"></script>
<script src="js/modal.js?v=<?php include 'php/templates/version.php'; ?>"></script>

View File

@@ -18,7 +18,7 @@ if(file_exists($filename)) {
if(trim($fileContents) === 'Dev') {
echo date('H:i:s') . " - " . $fileContents;
} else {
echo $fileContents;
echo trim($fileContents);
}
}
else {