GraphQl 0.11.17.1 - better api_token initialization + device tiles

This commit is contained in:
jokob-sk
2024-11-17 14:35:39 +11:00
parent 6407ee5c13
commit 78fc9214bb
6 changed files with 140 additions and 66 deletions

View File

@@ -171,6 +171,9 @@ function main () {
showSpinner(); showSpinner();
// render tiles
getDevicesTotals();
//initialize the table headers in the correct order //initialize the table headers in the correct order
var availableColumns = getSettingOptions("UI_device_columns").split(","); var availableColumns = getSettingOptions("UI_device_columns").split(",");
headersDefaultOrder = availableColumns.map(val => getString(val)); headersDefaultOrder = availableColumns.map(val => getString(val));
@@ -223,60 +226,77 @@ function mapIndx(oldIndex)
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// Query total numbers of Devices by status // Query total numbers of Devices by status
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
function getDevicesTotals(devicesData) { function getDevicesTotals() {
// Check cache first
let resultJSON = getCache("getDevicesTotals");
let resultJSON = ""; if (resultJSON !== "") {
resultJSON = JSON.parse(resultJSON);
if (getCache("getDevicesTotals") !== "") { processDeviceTotals(resultJSON);
resultJSON = getCache("getDevicesTotals");
} else { } else {
// Fetch data via AJAX
$.ajax({
url: "/api/table_devices_tiles.json",
type: "GET",
dataType: "json",
success: function(response) {
if (response && response.data) {
resultJSON = response.data[0]; // Assuming the structure {"data": [ ... ]}
// Save the result to cache
setCache("getDevicesTotals", JSON.stringify(resultJSON));
// Define filter conditions and corresponding objects // Process the fetched data
const filters = [ processDeviceTotals(resultJSON);
{ status: 'my_devices', color: 'bg-aqua', label: getString('Device_Shortcut_AllDevices'), icon: 'fa-laptop' }, } else {
{ status: 'all', color: 'bg-aqua', label: getString('Gen_All_Devices'), icon: 'fa-laptop' }, console.error("Invalid response format from API");
{ status: 'connected', color: 'bg-green', label: getString('Device_Shortcut_Connected'), icon: 'fa-plug' }, }
{ status: 'favorites', color: 'bg-yellow', label: getString('Device_Shortcut_Favorites'), icon: 'fa-star' }, },
{ status: 'new', color: 'bg-yellow', label: getString('Device_Shortcut_NewDevices'), icon: 'fa-plus' }, error: function(xhr, status, error) {
{ status: 'down', color: 'bg-red', label: getString('Device_Shortcut_DownOnly'), icon: 'fa-warning' }, console.error("Failed to fetch devices data:", error);
{ status: 'archived', color: 'bg-gray', label: getString('Device_Shortcut_Archived'), icon: 'fa-eye-slash' },
{ status: 'offline', color: 'bg-gray', label: getString('Gen_Offline'), icon: 'fa-xmark' }
];
// Initialize an empty array to store the final objects
let dataArray = [];
// Loop through each filter condition
filters.forEach(filter => {
// Calculate count dynamically based on filter condition
let count = filterDataByStatus(devicesData, filter.status).length;
// Check any condition to skip adding the object to dataArray
if (
(['', 'False'].includes(getSetting('UI_hide_empty')) || (getSetting('UI_hide_empty') == "True" && count > 0)) &&
(getSetting('UI_shown_cards') == "" || getSetting('UI_shown_cards').includes(filter.status))
) {
dataArray.push({
onclickEvent: `initializeDatatable('${filter.status}')`,
color: filter.color,
title: count,
label: filter.label,
icon: filter.icon
});
} }
}); });
}
}
// render info boxes/tile cards function processDeviceTotals(devicesData) {
renderInfoboxes( // Define filter conditions and corresponding objects
dataArray const filters = [
) { status: 'my_devices', color: 'bg-aqua', label: getString('Device_Shortcut_AllDevices'), icon: 'fa-laptop' },
{ status: 'all', color: 'bg-aqua', label: getString('Gen_All_Devices'), icon: 'fa-laptop' },
{ status: 'connected', color: 'bg-green', label: getString('Device_Shortcut_Connected'), icon: 'fa-plug' },
{ status: 'favorites', color: 'bg-yellow', label: getString('Device_Shortcut_Favorites'), icon: 'fa-star' },
{ status: 'new', color: 'bg-yellow', label: getString('Device_Shortcut_NewDevices'), icon: 'fa-plus' },
{ status: 'down', color: 'bg-red', label: getString('Device_Shortcut_DownOnly'), icon: 'fa-warning' },
{ status: 'archived', color: 'bg-gray', label: getString('Device_Shortcut_Archived'), icon: 'fa-eye-slash' },
{ status: 'offline', color: 'bg-gray', label: getString('Gen_Offline'), icon: 'fa-xmark' }
];
// save to cache // Initialize an empty array to store the final objects
setCache("getDevicesTotals", resultJSON); let dataArray = [];
}
// console.log(resultJSON); // Loop through each filter condition
filters.forEach(filter => {
// Get count directly from API response data
let count = devicesData[filter.status] || 0;
// Check any condition to skip adding the object to dataArray
if (
(['', 'False'].includes(getSetting('UI_hide_empty')) || (getSetting('UI_hide_empty') == "True" && count > 0)) &&
(getSetting('UI_shown_cards') == "" || getSetting('UI_shown_cards').includes(filter.status))
) {
dataArray.push({
onclickEvent: `initializeDatatable('${filter.status}')`,
color: filter.color,
title: count,
label: filter.label,
icon: filter.icon
});
}
});
// Render info boxes/tile cards
renderInfoboxes(dataArray);
} }
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
@@ -340,6 +360,7 @@ function filterDataByStatus(data, status) {
// Map column index to column name for GraphQL query // Map column index to column name for GraphQL query
function mapColumnIndexToFieldName(index, tableColumnVisible) { function mapColumnIndexToFieldName(index, tableColumnVisible) {
// the order is important, don't change it!
const columnNames = [ const columnNames = [
"devName", "devName",
"devOwner", "devOwner",
@@ -367,6 +388,8 @@ function mapColumnIndexToFieldName(index, tableColumnVisible) {
"devSourcePlugin" "devSourcePlugin"
]; ];
console.log("OrderBy: " + columnNames[tableColumnOrder[index]]);
return columnNames[tableColumnOrder[index]] || null; return columnNames[tableColumnOrder[index]] || null;
} }
@@ -522,7 +545,7 @@ function initializeDatatable (status) {
device.devParentChildrenCount || 0, device.devParentChildrenCount || 0,
device.devLocation || "", device.devLocation || "",
device.devVendor || "", device.devVendor || "",
device.devParentPort || 0, device.devParentPort || "",
device.devGUID || "", device.devGUID || "",
device.devSyncHubNode || "", device.devSyncHubNode || "",
device.devSite || "", device.devSite || "",
@@ -708,6 +731,7 @@ function initializeDatatable (status) {
}, },
initComplete: function (settings, devices) { initComplete: function (settings, devices) {
// Handle any additional interactions or event listeners as required // Handle any additional interactions or event listeners as required
// Save cookie Rows displayed, and Parameters rows & order // Save cookie Rows displayed, and Parameters rows & order
$('#tableDevices').on( 'length.dt', function ( e, settings, len ) { $('#tableDevices').on( 'length.dt', function ( e, settings, len ) {
setCookie ("nax_parTableRows", len, 129600); // save for 90 days setCookie ("nax_parTableRows", len, 129600); // save for 90 days

View File

@@ -185,12 +185,12 @@ def main ():
db.commitDB() db.commitDB()
# Footer # Footer
updateState("Process: Wait")
mylog('verbose', ['[MAIN] Process: Wait']) mylog('verbose', ['[MAIN] Process: Wait'])
else: else:
# do something # do something
# mylog('verbose', ['[MAIN] Waiting to start next loop']) # mylog('verbose', ['[MAIN] Waiting to start next loop'])
dummyVariable = 1 updateState("Process: Wait")
#loop #loop

View File

@@ -3,9 +3,9 @@ import json
# Register NetAlertX modules # Register NetAlertX modules
import conf import conf
from const import (apiPath, sql_appevents, sql_devices_all, sql_events_pending_alert, sql_settings, sql_plugins_events, sql_plugins_history, sql_plugins_objects,sql_language_strings, sql_notifications_all, sql_online_history) from const import (apiPath, sql_appevents, sql_devices_all, sql_events_pending_alert, sql_settings, sql_plugins_events, sql_plugins_history, sql_plugins_objects,sql_language_strings, sql_notifications_all, sql_online_history, sql_devices_tiles)
from logger import mylog from logger import mylog
from helper import write_file, get_setting_value from helper import write_file, get_setting_value, updateState
# Import the start_server function # Import the start_server function
from graphql_server.graphql_server_start import start_server from graphql_server.graphql_server_start import start_server
@@ -17,8 +17,10 @@ apiEndpoints = []
#=============================================================================== #===============================================================================
def update_api(db, all_plugins, isNotification = False, updateOnlyDataSources = []): def update_api(db, all_plugins, isNotification = False, updateOnlyDataSources = []):
mylog('debug', ['[API] Update API starting']) mylog('debug', ['[API] Update API starting'])
# return
# update app_state.json and retrieve app_state to chjeck if GraphQL server is running
app_state = updateState("Update: API", None, None, None, None)
folder = apiPath folder = apiPath
# Save plugins # Save plugins
@@ -36,6 +38,7 @@ def update_api(db, all_plugins, isNotification = False, updateOnlyDataSources =
["plugins_language_strings", sql_language_strings], ["plugins_language_strings", sql_language_strings],
["notifications", sql_notifications_all], ["notifications", sql_notifications_all],
["online_history", sql_online_history], ["online_history", sql_online_history],
["devices_tiles", sql_devices_tiles],
["custom_endpoint", conf.API_CUSTOM_SQL], ["custom_endpoint", conf.API_CUSTOM_SQL],
] ]
@@ -50,15 +53,17 @@ def update_api(db, all_plugins, isNotification = False, updateOnlyDataSources =
graphql_port_value = get_setting_value("GRAPHQL_PORT") graphql_port_value = get_setting_value("GRAPHQL_PORT")
api_token_value = get_setting_value("API_TOKEN") api_token_value = get_setting_value("API_TOKEN")
# Validate and start the server if settings are available # start GraphQL server if not yet running
if graphql_port_value is not None and api_token_value is not None: if app_state.graphQLServerStarted == 0:
try: # Validate if settings are available
graphql_port_value = int(graphql_port_value) # Ensure port is an integer if graphql_port_value is not None and len(api_token_value) > 1:
start_server(graphql_port=graphql_port_value) # Start the server try:
except ValueError: graphql_port_value = int(graphql_port_value) # Ensure port is an integer
mylog('none', [f"[API] Invalid GRAPHQL_PORT value, must be an integer: {graphql_port_value}"]) start_server(graphql_port_value, app_state) # Start the server
else: except ValueError:
mylog('none', [f"[API] GRAPHQL_PORT or API_TOKEN is not set, will try later."]) mylog('none', [f"[API] Invalid GRAPHQL_PORT value, must be an integer: {graphql_port_value}"])
else:
mylog('none', [f"[API] GRAPHQL_PORT or API_TOKEN is not set, will try later."])
#------------------------------------------------------------------------------- #-------------------------------------------------------------------------------

View File

@@ -42,6 +42,38 @@ sql_devices_all = """
FROM Devices FROM Devices
""" """
sql_appevents = """select * from AppEvents""" sql_appevents = """select * from AppEvents"""
# The below query calculates counts of devices in various categories:
# (connected/online, offline, down, new, archived),
# as well as a combined count for devices that match any status listed in the UI_MY_DEVICES setting
sql_devices_tiles = """
WITH Statuses AS (
SELECT Value
FROM Settings
WHERE Code_Name = 'UI_MY_DEVICES'
),
MyDevicesFilter AS (
SELECT
-- Build a dynamic filter for devices matching any status in UI_MY_DEVICES
devPresentLastScan, devAlertDown, devIsNew, devIsArchived
FROM Devices
WHERE
(instr((SELECT Value FROM Statuses), 'online') > 0 AND devPresentLastScan = 1) OR
(instr((SELECT Value FROM Statuses), 'offline') > 0 AND devPresentLastScan = 0) OR
(instr((SELECT Value FROM Statuses), 'down') > 0 AND devPresentLastScan = 0 AND devAlertDown = 1) OR
(instr((SELECT Value FROM Statuses), 'new') > 0 AND devIsNew = 1) OR
(instr((SELECT Value FROM Statuses), 'archived') > 0 AND devIsArchived = 1)
)
SELECT
-- Counts for each individual status
(SELECT COUNT(*) FROM Devices WHERE devPresentLastScan = 1) AS connected,
(SELECT COUNT(*) FROM Devices WHERE devPresentLastScan = 0) AS offline,
(SELECT COUNT(*) FROM Devices WHERE devPresentLastScan = 0 AND devAlertDown = 1) AS down,
(SELECT COUNT(*) FROM Devices WHERE devIsNew = 1) AS new,
(SELECT COUNT(*) FROM Devices WHERE devIsArchived = 1) AS archived,
-- My Devices count
(SELECT COUNT(*) FROM MyDevicesFilter) AS my_devices
FROM Statuses;
"""
sql_devices_stats = """SELECT Online_Devices as online, Down_Devices as down, All_Devices as 'all', Archived_Devices as archived, sql_devices_stats = """SELECT Online_Devices as online, Down_Devices as down, All_Devices as 'all', Archived_Devices as archived,
(select count(*) from Devices a where devIsNew = 1 ) as new, (select count(*) from Devices a where devIsNew = 1 ) as new,
(select count(*) from Devices a where devName = '(unknown)' or devName = '(name not found)' ) as unknown (select count(*) from Devices a where devName = '(unknown)' or devName = '(name not found)' ) as unknown

View File

@@ -62,6 +62,7 @@ class Device(ObjectType):
devIsRandomMac = Int() devIsRandomMac = Int()
devParentChildrenCount = Int() devParentChildrenCount = Int()
devIpLong = Int() devIpLong = Int()
devFilterStatus = String()
class DeviceResult(ObjectType): class DeviceResult(ObjectType):
@@ -92,6 +93,13 @@ class Query(ObjectType):
mylog('none', f'[graphql_schema] devices_data: {devices_data}') mylog('none', f'[graphql_schema] devices_data: {devices_data}')
# Define static list of searchable fields
searchable_fields = [
"devName", "devMac", "devOwner", "devType", "devVendor",
"devGroup", "devComments", "devLocation", "devStatus",
"devSSID", "devSite", "devSourcePlugin", "devSyncHubNode"
]
# Apply sorting if options are provided # Apply sorting if options are provided
if options: if options:
if options.sort: if options.sort:
@@ -104,9 +112,14 @@ class Query(ObjectType):
# Filter data if a search term is provided # Filter data if a search term is provided
if options.search: if options.search:
search_term = options.search.lower()
devices_data = [ devices_data = [
device for device in devices_data device for device in devices_data
if options.search.lower() in device.get("devName", "").lower() if any(
search_term in str(device.get(field, "")).lower()
for field in searchable_fields # Search only predefined fields
)
] ]
# Then apply pagination # Then apply pagination

View File

@@ -38,11 +38,11 @@ def graphql_endpoint():
# Return the result as JSON # Return the result as JSON
return jsonify(result.data) return jsonify(result.data)
def start_server(graphql_port): def start_server(graphql_port, app_state):
"""Start the GraphQL server in a background thread.""" """Start the GraphQL server in a background thread."""
state = updateState("GraphQL: Starting", None, None, None, None)
if state.graphQLServerStarted == 0: if app_state.graphQLServerStarted == 0:
mylog('verbose', [f'[graphql_server] Starting on port: {graphql_port}']) mylog('verbose', [f'[graphql_server] Starting on port: {graphql_port}'])
# Start Flask app in a separate thread # Start Flask app in a separate thread
@@ -57,4 +57,4 @@ def start_server(graphql_port):
thread.start() thread.start()
# Update the state to indicate the server has started # Update the state to indicate the server has started
state = updateState("Process: Wait", None, None, None, 1) app_state = updateState("Process: Wait", None, None, None, 1)