mirror of
https://github.com/jokob-sk/NetAlertX.git
synced 2026-04-15 22:51:37 -07:00
FE+BE: qppEvents refactor and graphql endpoint
Signed-off-by: jokob-sk <jokob.sk@gmail.com>
This commit is contained in:
+146
-88
@@ -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">
|
<a target="_blank" href="https://github.com/jokob-sk/NetAlertX/blob/main/docs/WORKFLOWS_DEBUGGING.md">
|
||||||
<i class="fa fa-circle-question"></i>
|
<i class="fa fa-circle-question"></i>
|
||||||
</a>
|
</a>
|
||||||
@@ -21,114 +21,172 @@
|
|||||||
// show loading dialog
|
// show loading dialog
|
||||||
showSpinner()
|
showSpinner()
|
||||||
|
|
||||||
$(document).ready(function() {
|
$(document).ready(function () {
|
||||||
|
|
||||||
// Load JSON data from the provided URL
|
const protocol = window.location.protocol.replace(':', '');
|
||||||
$.getJSON('php/server/query_json.php?file=table_appevents.json', function(data) {
|
const host = window.location.hostname;
|
||||||
// Process the JSON data and generate UI dynamically
|
const apiToken = getSetting("API_TOKEN");
|
||||||
processData(data)
|
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({
|
$('#appevents-table').DataTable({
|
||||||
data: allData,
|
processing: true,
|
||||||
|
serverSide: true,
|
||||||
paging: true,
|
paging: true,
|
||||||
lengthChange: true,
|
|
||||||
lengthMenu: [[10, 25, 50, 100, 500, -1], [10, 25, 50, 100, 500, 'All']],
|
|
||||||
searching: true,
|
searching: true,
|
||||||
ordering: true,
|
ordering: true,
|
||||||
info: true,
|
pageLength: 25,
|
||||||
autoWidth: false,
|
lengthMenu: [[10, 25, 50, 100], [10, 25, 50, 100]],
|
||||||
pageLength: 25, // Set the default paging to 25
|
|
||||||
|
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: [
|
columns: [
|
||||||
{ data: 'DateTimeCreated', title: getString('AppEvents_DateTimeCreated') },
|
{ data: 'DateTimeCreated', title: getString('AppEvents_DateTimeCreated') },
|
||||||
{ data: 'AppEventProcessed', title: getString('AppEvents_AppEventProcessed') },
|
{ 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: 'ObjectType', title: getString('AppEvents_ObjectType') },
|
||||||
{ data: 'ObjectPrimaryID', title: getString('AppEvents_ObjectPrimaryID') },
|
{ data: 'ObjectPrimaryID', title: getString('AppEvents_ObjectPrimaryID') },
|
||||||
{ data: 'ObjectSecondaryID', title: getString('AppEvents_ObjectSecondaryID') },
|
{ data: 'ObjectSecondaryID', title: getString('AppEvents_ObjectSecondaryID') },
|
||||||
{ data: 'ObjectStatus', title: getString('AppEvents_ObjectStatus') },
|
{ data: 'ObjectStatus', title: getString('AppEvents_ObjectStatus') },
|
||||||
{ data: 'ObjectPlugin', title: getString('AppEvents_Plugin') },
|
{ data: 'ObjectPlugin', title: getString('AppEvents_Plugin') },
|
||||||
{ data: 'ObjectGUID', title: "Object GUID" },
|
{ data: 'ObjectGUID', title: 'Object GUID' },
|
||||||
{ data: 'GUID', title: "Event GUID" },
|
{ data: 'GUID', title: 'Event GUID' }
|
||||||
// Add other columns as needed
|
|
||||||
],
|
],
|
||||||
// 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 = {
|
let options = {
|
||||||
year: 'numeric',
|
year: 'numeric',
|
||||||
month: 'short',
|
month: 'short',
|
||||||
day: '2-digit',
|
day: '2-digit',
|
||||||
hour: '2-digit',
|
hour: '2-digit',
|
||||||
minute: '2-digit',
|
minute: '2-digit',
|
||||||
second: '2-digit',
|
second: '2-digit',
|
||||||
hour12: false, // Use 24-hour format
|
hour12: false,
|
||||||
timeZone: timezone // Use the specified timezone
|
timeZone: timezone
|
||||||
};
|
};
|
||||||
|
|
||||||
let localDate = new Intl.DateTimeFormat('en-GB', options).format(utcDate);
|
$(td).html(
|
||||||
|
new Intl.DateTimeFormat('en-GB', options).format(utcDate)
|
||||||
// Update the table cell
|
);
|
||||||
$(td).html(localDate);
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
// Activate the first tab
|
});
|
||||||
$('#tabs-location li:first-child').addClass('active');
|
|
||||||
$('#tabs-content-location .tab-pane:first-child').addClass('active');
|
|
||||||
}
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -75,6 +75,7 @@ CORS(
|
|||||||
r"/sessions/*": {"origins": "*"},
|
r"/sessions/*": {"origins": "*"},
|
||||||
r"/settings/*": {"origins": "*"},
|
r"/settings/*": {"origins": "*"},
|
||||||
r"/dbquery/*": {"origins": "*"},
|
r"/dbquery/*": {"origins": "*"},
|
||||||
|
r"/graphql/*": {"origins": "*"},
|
||||||
r"/messaging/*": {"origins": "*"},
|
r"/messaging/*": {"origins": "*"},
|
||||||
r"/events/*": {"origins": "*"},
|
r"/events/*": {"origins": "*"},
|
||||||
r"/logs/*": {"origins": "*"},
|
r"/logs/*": {"origins": "*"},
|
||||||
|
|||||||
@@ -133,6 +133,42 @@ class LangStringResult(ObjectType):
|
|||||||
count = Int()
|
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
|
# Define Query Type with Pagination Support
|
||||||
class Query(ObjectType):
|
class Query(ObjectType):
|
||||||
# --- DEVICES ---
|
# --- DEVICES ---
|
||||||
@@ -347,6 +383,83 @@ class Query(ObjectType):
|
|||||||
|
|
||||||
return SettingResult(settings=settings, count=len(settings))
|
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 ---
|
||||||
langStrings = Field(
|
langStrings = Field(
|
||||||
LangStringResult,
|
LangStringResult,
|
||||||
|
|||||||
Reference in New Issue
Block a user