FE+BE: qppEvents refactor and graphql endpoint

Signed-off-by: jokob-sk <jokob.sk@gmail.com>
This commit is contained in:
jokob-sk
2025-12-25 10:19:01 +11:00
parent fbb5dcf11c
commit a8cac85a11
3 changed files with 260 additions and 88 deletions

View File

@@ -1,4 +1,4 @@
<span class="helpIcon">
<span class="helpIcon">
<a target="_blank" href="https://github.com/jokob-sk/NetAlertX/blob/main/docs/WORKFLOWS_DEBUGGING.md">
<i class="fa fa-circle-question"></i>
</a>
@@ -21,114 +21,172 @@
// show loading dialog
showSpinner()
$(document).ready(function() {
$(document).ready(function () {
// Load JSON data from the provided URL
$.getJSON('php/server/query_json.php?file=table_appevents.json', function(data) {
// Process the JSON data and generate UI dynamically
processData(data)
const protocol = window.location.protocol.replace(':', '');
const host = window.location.hostname;
const apiToken = getSetting("API_TOKEN");
const port = getSetting("GRAPHQL_PORT");
const graphqlUrl = `${protocol}://${host}:${port}/graphql`;
// hide loading dialog
hideSpinner()
});
});
function processData(data) {
// Create an object to store unique ObjectType values as app event identifiers
var appEventIdentifiers = {};
// Array to accumulate data for DataTable
var allData = [];
// Iterate through the data and generate tabs and content dynamically
$.each(data.data, function(index, item) {
// Accumulate data for DataTable
allData.push(item);
});
console.log(allData);
// Initialize DataTable for all app events
$('#appevents-table').DataTable({
data: allData,
processing: true,
serverSide: true,
paging: true,
lengthChange: true,
lengthMenu: [[10, 25, 50, 100, 500, -1], [10, 25, 50, 100, 500, 'All']],
searching: true,
ordering: true,
info: true,
autoWidth: false,
pageLength: 25, // Set the default paging to 25
pageLength: 25,
lengthMenu: [[10, 25, 50, 100], [10, 25, 50, 100]],
ajax: function (dtRequest, callback) {
const page = Math.floor(dtRequest.start / dtRequest.length) + 1;
const limit = dtRequest.length;
// ---- SEARCH ----
const searchValue = dtRequest.search?.value || null;
// ---- SORTING ----
let sort = [];
if (dtRequest.order && dtRequest.order.length > 0) {
const order = dtRequest.order[0];
const columnName = dtRequest.columns[order.column].data;
sort.push({
field: columnName,
order: order.dir
});
}
const query = `
query AppEvents($options: PageQueryOptionsInput) {
appEvents(options: $options) {
count
appEvents {
DateTimeCreated
AppEventProcessed
AppEventType
ObjectType
ObjectPrimaryID
ObjectSecondaryID
ObjectStatus
ObjectPlugin
ObjectGUID
GUID
}
}
}
`;
const variables = {
options: {
page: page,
limit: limit,
search: searchValue,
sort: sort
}
};
$.ajax({
method: "POST",
url: graphqlUrl,
headers: {
"Authorization": "Bearer " + apiToken,
"Content-Type": "application/json"
},
data: JSON.stringify({
query: query,
variables: variables
}),
success: function (response) {
if (response.errors) {
console.error(response.errors);
callback({
data: [],
recordsTotal: 0,
recordsFiltered: 0
});
return;
}
const result = response.data.appEvents;
callback({
data: result.appEvents,
recordsTotal: result.count,
recordsFiltered: result.count
});
hideSpinner();
},
error: function () {
callback({
data: [],
recordsTotal: 0,
recordsFiltered: 0
});
}
});
},
columns: [
{ data: 'DateTimeCreated', title: getString('AppEvents_DateTimeCreated') },
{ data: 'AppEventProcessed', title: getString('AppEvents_AppEventProcessed') },
{ data: 'AppEventType', title: getString('AppEvents_Type') },
{ data: 'AppEventType', title: getString('AppEvents_Type') },
{ data: 'ObjectType', title: getString('AppEvents_ObjectType') },
{ data: 'ObjectPrimaryID', title: getString('AppEvents_ObjectPrimaryID') },
{ data: 'ObjectSecondaryID', title: getString('AppEvents_ObjectSecondaryID') },
{ data: 'ObjectStatus', title: getString('AppEvents_ObjectStatus') },
{ data: 'ObjectPlugin', title: getString('AppEvents_Plugin') },
{ data: 'ObjectGUID', title: "Object GUID" },
{ data: 'GUID', title: "Event GUID" },
// Add other columns as needed
{ data: 'ObjectStatus', title: getString('AppEvents_ObjectStatus') },
{ data: 'ObjectPlugin', title: getString('AppEvents_Plugin') },
{ data: 'ObjectGUID', title: 'Object GUID' },
{ data: 'GUID', title: 'Event GUID' }
],
// Add column-specific configurations if needed
columnDefs: [
{ className: 'text-center', targets: [4] },
{ width: '80px', targets: [7] },
// ... Add other columnDefs as needed
// Full MAC
{targets: [4, 5],
'createdCell': function (td, cellData, rowData, row, col) {
if (!emptyArr.includes(cellData)){
$(td).html (createDeviceLink(cellData));
} else {
$(td).html ('');
}
} },
// Processed
{targets: [1],
'createdCell': function (td, cellData, rowData, row, col) {
// console.log(cellData);
$(td).html (cellData);
}
},
// Datetime
{targets: [0],
'createdCell': function (td, cellData, rowData, row, col) {
let timezone = $("#NAX_TZ").html(); // e.g., 'Europe/Berlin'
let utcDate = new Date(cellData + ' UTC'); // Adding ' UTC' makes it interpreted as UTC time
// Format the date in the desired timezone
columnDefs: [
{ className: 'text-center', targets: [1, 4] },
{ width: '90px', targets: [7] },
// Device links
{
targets: [4, 5],
createdCell: function (td, cellData) {
if (!emptyArr.includes(cellData)) {
$(td).html(createDeviceLink(cellData));
} else {
$(td).html('');
}
}
},
// Date formatting
{
targets: [0],
createdCell: function (td, cellData) {
let timezone = $("#NAX_TZ").html();
let utcDate = new Date(cellData + ' UTC');
let options = {
year: 'numeric',
month: 'short',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
hour12: false, // Use 24-hour format
timeZone: timezone // Use the specified timezone
year: 'numeric',
month: 'short',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
hour12: false,
timeZone: timezone
};
let localDate = new Intl.DateTimeFormat('en-GB', options).format(utcDate);
// Update the table cell
$(td).html(localDate);
}
},
$(td).html(
new Intl.DateTimeFormat('en-GB', options).format(utcDate)
);
}
}
]
});
// Activate the first tab
$('#tabs-location li:first-child').addClass('active');
$('#tabs-content-location .tab-pane:first-child').addClass('active');
}
});
</script>

View File

@@ -75,6 +75,7 @@ CORS(
r"/sessions/*": {"origins": "*"},
r"/settings/*": {"origins": "*"},
r"/dbquery/*": {"origins": "*"},
r"/graphql/*": {"origins": "*"},
r"/messaging/*": {"origins": "*"},
r"/events/*": {"origins": "*"},
r"/logs/*": {"origins": "*"},

View File

@@ -133,6 +133,42 @@ class LangStringResult(ObjectType):
count = Int()
# --- APP EVENTS ---
class AppEvent(ObjectType):
Index = Int()
GUID = String()
AppEventProcessed = Int()
DateTimeCreated = String()
ObjectType = String()
ObjectGUID = String()
ObjectPlugin = String()
ObjectPrimaryID = String()
ObjectSecondaryID = String()
ObjectForeignKey = String()
ObjectIndex = Int()
ObjectIsNew = Int()
ObjectIsArchived = Int()
ObjectStatusColumn = String()
ObjectStatus = String()
AppEventType = String()
Helper1 = String()
Helper2 = String()
Helper3 = String()
Extra = String()
class AppEventResult(ObjectType):
appEvents = List(AppEvent)
count = Int()
# ----------------------------------------------------------------------------------------------
# Define Query Type with Pagination Support
class Query(ObjectType):
# --- DEVICES ---
@@ -347,6 +383,83 @@ class Query(ObjectType):
return SettingResult(settings=settings, count=len(settings))
# --- APP EVENTS ---
appEvents = Field(AppEventResult, options=PageQueryOptionsInput())
def resolve_appEvents(self, info, options=None):
try:
with open(folder + "table_appevents.json", "r") as f:
events_data = json.load(f).get("data", [])
except (FileNotFoundError, json.JSONDecodeError) as e:
mylog("none", f"[graphql_schema] Error loading app events data: {e}")
return AppEventResult(appEvents=[], count=0)
mylog("trace", f"[graphql_schema] Loaded {len(events_data)} app events")
# total count BEFORE pagination (after filters/search)
total_count = len(events_data)
if options:
# --------------------
# SEARCH
# --------------------
if options.search:
search_term = options.search.lower()
searchable_fields = [
"GUID",
"ObjectType",
"ObjectGUID",
"ObjectPlugin",
"ObjectPrimaryID",
"ObjectSecondaryID",
"ObjectStatus",
"AppEventType",
"Helper1",
"Helper2",
"Helper3",
"Extra",
]
events_data = [
e for e in events_data
if any(
search_term in str(e.get(field, "")).lower()
for field in searchable_fields
)
]
# --------------------
# SORTING
# --------------------
if options.sort:
for sort_option in reversed(options.sort):
events_data = sorted(
events_data,
key=lambda x: mixed_type_sort_key(
x.get(sort_option.field)
),
reverse=(sort_option.order.lower() == "desc"),
)
# update count AFTER filters/search, BEFORE pagination
total_count = len(events_data)
# --------------------
# PAGINATION
# --------------------
if options.page and options.limit:
start = (options.page - 1) * options.limit
end = start + options.limit
events_data = events_data[start:end]
events = [AppEvent(**event) for event in events_data]
return AppEventResult(
appEvents=events,
count=total_count
)
# --- LANGSTRINGS ---
langStrings = Field(
LangStringResult,