mirror of
https://github.com/jokob-sk/NetAlertX.git
synced 2026-04-05 01:31:49 -07:00
feat(api): Enhance session events API with pagination, sorting, and filtering
- Added support for pagination (page and limit) in the session events endpoint. - Implemented sorting functionality based on specified columns and directions. - Introduced free-text search capability for session events. - Updated SQL queries to retrieve all events and added a new SQL constant for events. - Refactored GraphQL types and helpers to support new plugin and event queries. - Created new GraphQL resolvers for plugins and events with pagination and filtering. - Added comprehensive tests for new GraphQL endpoints and session events functionality.
This commit is contained in:
@@ -32,51 +32,64 @@
|
||||
|
||||
function loadEventsData() {
|
||||
const hideConnections = $('#chkHideConnectionEvents')[0].checked;
|
||||
const hideConnectionsStr = hideConnections ? 'true' : 'false';
|
||||
|
||||
let period = $("#period").val();
|
||||
let { start, end } = getPeriodStartEnd(period);
|
||||
|
||||
const rawSql = `
|
||||
SELECT eveDateTime, eveEventType, eveIp, eveAdditionalInfo
|
||||
FROM Events
|
||||
WHERE eveMac = "${mac}"
|
||||
AND eveDateTime BETWEEN "${start}" AND "${end}"
|
||||
AND (
|
||||
(eveEventType NOT IN ("Connected", "Disconnected", "VOIDED - Connected", "VOIDED - Disconnected"))
|
||||
OR "${hideConnectionsStr}" = "false"
|
||||
)
|
||||
const apiToken = getSetting("API_TOKEN");
|
||||
const apiBase = getApiBase();
|
||||
const graphqlUrl = `${apiBase}/graphql`;
|
||||
|
||||
const query = `
|
||||
query Events($options: EventQueryOptionsInput) {
|
||||
events(options: $options) {
|
||||
count
|
||||
entries {
|
||||
eveDateTime
|
||||
eveEventType
|
||||
eveIp
|
||||
eveAdditionalInfo
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const apiToken = getSetting("API_TOKEN");
|
||||
|
||||
const apiBaseUrl = getApiBase();
|
||||
const url = `${apiBaseUrl}/dbquery/read`;
|
||||
|
||||
$.ajax({
|
||||
url: url,
|
||||
url: graphqlUrl,
|
||||
method: "POST",
|
||||
contentType: "application/json",
|
||||
headers: {
|
||||
"Authorization": `Bearer ${apiToken}`
|
||||
},
|
||||
data: JSON.stringify({
|
||||
rawSql: btoa(rawSql)
|
||||
query,
|
||||
variables: {
|
||||
options: {
|
||||
eveMac: mac,
|
||||
dateFrom: start,
|
||||
dateTo: end,
|
||||
limit: 500,
|
||||
sort: [{ field: "eveDateTime", order: "desc" }]
|
||||
}
|
||||
}
|
||||
}),
|
||||
success: function (data) {
|
||||
// assuming read_query returns rows directly
|
||||
const rows = data["results"].map(row => {
|
||||
const rawDate = row.eveDateTime;
|
||||
const formattedDate = rawDate ? localizeTimestamp(rawDate) : '-';
|
||||
const CONNECTION_TYPES = ["Connected", "Disconnected", "VOIDED - Connected", "VOIDED - Disconnected"];
|
||||
|
||||
return [
|
||||
formattedDate,
|
||||
row.eveDateTime,
|
||||
row.eveEventType,
|
||||
row.eveIp,
|
||||
row.eveAdditionalInfo
|
||||
];
|
||||
});
|
||||
const rows = data.data.events.entries
|
||||
.filter(row => !hideConnections || !CONNECTION_TYPES.includes(row.eveEventType))
|
||||
.map(row => {
|
||||
const rawDate = row.eveDateTime;
|
||||
const formattedDate = rawDate ? localizeTimestamp(rawDate) : '-';
|
||||
|
||||
return [
|
||||
formattedDate,
|
||||
row.eveDateTime,
|
||||
row.eveEventType,
|
||||
row.eveIp,
|
||||
row.eveAdditionalInfo
|
||||
];
|
||||
});
|
||||
|
||||
const table = $('#tableEvents').DataTable();
|
||||
table.clear();
|
||||
|
||||
111
front/events.php
111
front/events.php
@@ -105,21 +105,64 @@ function main() {
|
||||
$('#period').val(period);
|
||||
initializeDatatable();
|
||||
getEventsTotals();
|
||||
getEvents(eventsType);
|
||||
getEvents(eventsType); // triggers first serverSide draw
|
||||
}
|
||||
|
||||
/* ---------------- Initialize DataTable ---------------- */
|
||||
function initializeDatatable() {
|
||||
const table = $('#tableEvents').DataTable({
|
||||
paging: true,
|
||||
const apiBase = getApiBase();
|
||||
const apiToken = getSetting("API_TOKEN");
|
||||
|
||||
$('#tableEvents').DataTable({
|
||||
processing: true,
|
||||
serverSide: true,
|
||||
paging: true,
|
||||
lengthChange: true,
|
||||
lengthMenu: getLengthMenu(getSetting("UI_DEFAULT_PAGE_SIZE")),
|
||||
searching: true,
|
||||
ordering: true,
|
||||
info: true,
|
||||
autoWidth: false,
|
||||
order: [[0, "desc"], [3, "desc"], [5, "desc"]],
|
||||
pageLength: tableRows,
|
||||
lengthMenu: getLengthMenu(getSetting("UI_DEFAULT_PAGE_SIZE")),
|
||||
searching: true,
|
||||
ordering: true,
|
||||
info: true,
|
||||
autoWidth: false,
|
||||
order: [[0, "desc"]],
|
||||
pageLength: tableRows,
|
||||
|
||||
ajax: function (dtRequest, callback) {
|
||||
const page = Math.floor(dtRequest.start / dtRequest.length) + 1;
|
||||
const limit = dtRequest.length;
|
||||
const search = dtRequest.search?.value || '';
|
||||
const sortCol = dtRequest.order?.length ? dtRequest.order[0].column : 0;
|
||||
const sortDir = dtRequest.order?.length ? dtRequest.order[0].dir : 'desc';
|
||||
|
||||
const url = `${apiBase}/sessions/session-events`
|
||||
+ `?type=${encodeURIComponent(eventsType)}`
|
||||
+ `&period=${encodeURIComponent(period)}`
|
||||
+ `&page=${page}`
|
||||
+ `&limit=${limit}`
|
||||
+ `&sortCol=${sortCol}`
|
||||
+ `&sortDir=${sortDir}`
|
||||
+ (search ? `&search=${encodeURIComponent(search)}` : '');
|
||||
|
||||
$.ajax({
|
||||
url,
|
||||
method: "GET",
|
||||
dataType: "json",
|
||||
headers: { "Authorization": `Bearer ${apiToken}` },
|
||||
success: function (response) {
|
||||
callback({
|
||||
data: response.data || [],
|
||||
recordsTotal: response.total || 0,
|
||||
recordsFiltered: response.recordsFiltered || 0
|
||||
});
|
||||
hideSpinner();
|
||||
},
|
||||
error: function (xhr, status, error) {
|
||||
console.error("Error fetching session events:", status, error, xhr.responseText);
|
||||
callback({ data: [], recordsTotal: 0, recordsFiltered: 0 });
|
||||
hideSpinner();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
columnDefs: [
|
||||
{ targets: [0,5,6,7,8,10,11,12,13], visible: false },
|
||||
{ targets: [7], orderData: [8] },
|
||||
@@ -131,14 +174,14 @@ function initializeDatatable() {
|
||||
{ targets: [3], createdCell: (td, cellData) => $(td).html(localizeTimestamp(cellData)) },
|
||||
{ targets: [4,5,6,7], createdCell: (td, cellData) => $(td).html(translateHTMLcodes(cellData)) }
|
||||
],
|
||||
processing: true, // Shows "processing" overlay
|
||||
|
||||
language: {
|
||||
processing: '<table><td width="130px" align="middle"><?= lang("Events_Loading"); ?></td><td><i class="fa-solid fa-spinner fa-spin-pulse"></i></td></table>',
|
||||
emptyTable: 'No data',
|
||||
lengthMenu: "<?= lang('Events_Tablelenght'); ?>",
|
||||
search: "<?= lang('Events_Searchbox'); ?>: ",
|
||||
paginate: { next: "<?= lang('Events_Table_nav_next'); ?>", previous: "<?= lang('Events_Table_nav_prev'); ?>" },
|
||||
info: "<?= lang('Events_Table_info'); ?>"
|
||||
search: "<?= lang('Events_Searchbox'); ?>: ",
|
||||
paginate: { next: "<?= lang('Events_Table_nav_next'); ?>", previous: "<?= lang('Events_Table_nav_prev'); ?>" },
|
||||
info: "<?= lang('Events_Table_info'); ?>"
|
||||
}
|
||||
});
|
||||
|
||||
@@ -179,53 +222,33 @@ function getEventsTotals() {
|
||||
});
|
||||
}
|
||||
|
||||
/* ---------------- Fetch events and reload DataTable ---------------- */
|
||||
/* ---------------- Switch event type and reload DataTable ---------------- */
|
||||
function getEvents(type) {
|
||||
eventsType = type;
|
||||
const table = $('#tableEvents').DataTable();
|
||||
|
||||
// Event type config: title, color, session columns visibility
|
||||
const config = {
|
||||
all: {title: 'Events_Shortcut_AllEvents', color: 'aqua', sesionCols: false},
|
||||
sessions: {title: 'Events_Shortcut_Sessions', color: 'green', sesionCols: true},
|
||||
missing: {title: 'Events_Shortcut_MissSessions', color: 'yellow', sesionCols: true},
|
||||
voided: {title: 'Events_Shortcut_VoidSessions', color: 'yellow', sesionCols: false},
|
||||
new: {title: 'Events_Shortcut_NewDevices', color: 'yellow', sesionCols: false},
|
||||
down: {title: 'Events_Shortcut_DownAlerts', color: 'red', sesionCols: false}
|
||||
all: {title: 'Events_Shortcut_AllEvents', color: 'aqua', sesionCols: false},
|
||||
sessions: {title: 'Events_Shortcut_Sessions', color: 'green', sesionCols: true},
|
||||
missing: {title: 'Events_Shortcut_MissSessions', color: 'yellow', sesionCols: true},
|
||||
voided: {title: 'Events_Shortcut_VoidSessions', color: 'yellow', sesionCols: false},
|
||||
new: {title: 'Events_Shortcut_NewDevices', color: 'yellow', sesionCols: false},
|
||||
down: {title: 'Events_Shortcut_DownAlerts', color: 'red', sesionCols: false}
|
||||
}[type] || {title: 'Events_Shortcut_Events', color: '', sesionCols: false};
|
||||
|
||||
// Update title and color
|
||||
$('#tableEventsTitle').attr('class', 'box-title text-' + config.color).html(getString(config.title));
|
||||
$('#tableEventsBox').attr('class', 'box box-' + config.color);
|
||||
|
||||
// Toggle columns visibility
|
||||
// Toggle column visibility
|
||||
table.column(3).visible(!config.sesionCols);
|
||||
table.column(4).visible(!config.sesionCols);
|
||||
table.column(5).visible(config.sesionCols);
|
||||
table.column(6).visible(config.sesionCols);
|
||||
table.column(7).visible(config.sesionCols);
|
||||
|
||||
// Build API URL
|
||||
const apiBase = getApiBase();
|
||||
const apiToken = getSetting("API_TOKEN");
|
||||
const url = `${apiBase}/sessions/session-events?type=${encodeURIComponent(type)}&period=${encodeURIComponent(period)}`;
|
||||
|
||||
table.clear().draw(); // Clear old rows
|
||||
|
||||
showSpinner()
|
||||
|
||||
$.ajax({
|
||||
url,
|
||||
method: "GET",
|
||||
dataType: "json",
|
||||
headers: { "Authorization": `Bearer ${apiToken}` },
|
||||
beforeSend: showSpinner, // Show spinner during fetch
|
||||
complete: hideSpinner, // Hide spinner after fetch
|
||||
success: response => {
|
||||
const data = Array.isArray(response) ? response : response.data || [];
|
||||
table.rows.add(data).draw();
|
||||
},
|
||||
error: (xhr, status, error) => console.error("Error fetching session events:", status, error, xhr.responseText)
|
||||
});
|
||||
showSpinner();
|
||||
table.ajax.reload(null, true); // reset to page 1
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -273,26 +273,14 @@ function genericSaveData (id) {
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
pluginDefinitions = []
|
||||
pluginUnprocessedEvents = []
|
||||
pluginObjects = []
|
||||
pluginHistory = []
|
||||
|
||||
async function getData() {
|
||||
try {
|
||||
showSpinner();
|
||||
console.log("Plugins getData called");
|
||||
|
||||
const [plugins, events, objects, history] = await Promise.all([
|
||||
fetchJson('plugins.json'),
|
||||
fetchJson('table_plugins_events.json'),
|
||||
fetchJson('table_plugins_objects.json'),
|
||||
fetchJson('table_plugins_history.json')
|
||||
]);
|
||||
|
||||
const plugins = await fetchJson('plugins.json');
|
||||
pluginDefinitions = plugins.data;
|
||||
pluginUnprocessedEvents = events.data;
|
||||
pluginObjects = objects.data;
|
||||
pluginHistory = history.data;
|
||||
|
||||
generateTabs();
|
||||
} catch (err) {
|
||||
@@ -306,6 +294,106 @@ async function fetchJson(filename) {
|
||||
return await response.json();
|
||||
}
|
||||
|
||||
// GraphQL helper — fires a paginated plugin table query and calls back with
|
||||
// the DataTables-compatible response plus the raw GraphQL result object.
|
||||
function postPluginGraphQL(gqlField, prefix, foreignKey, dtRequest, callback) {
|
||||
const apiToken = getSetting("API_TOKEN");
|
||||
const apiBase = getApiBase();
|
||||
const page = Math.floor(dtRequest.start / dtRequest.length) + 1;
|
||||
const limit = dtRequest.length;
|
||||
const search = dtRequest.search?.value || null;
|
||||
|
||||
let sort = [];
|
||||
if (dtRequest.order?.length > 0) {
|
||||
const order = dtRequest.order[0];
|
||||
sort.push({ field: dtRequest.columns[order.column].data, order: order.dir });
|
||||
}
|
||||
|
||||
const query = `
|
||||
query PluginData($options: PluginQueryOptionsInput) {
|
||||
${gqlField}(options: $options) {
|
||||
count
|
||||
dbCount
|
||||
entries {
|
||||
index plugin objectPrimaryId objectSecondaryId
|
||||
dateTimeCreated dateTimeChanged
|
||||
watchedValue1 watchedValue2 watchedValue3 watchedValue4
|
||||
status extra userData foreignKey
|
||||
syncHubNodeName helpVal1 helpVal2 helpVal3 helpVal4 objectGuid
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
$.ajax({
|
||||
method: "POST",
|
||||
url: `${apiBase}/graphql`,
|
||||
headers: { "Authorization": `Bearer ${apiToken}`, "Content-Type": "application/json" },
|
||||
data: JSON.stringify({
|
||||
query,
|
||||
variables: { options: { page, limit, search, sort, plugin: prefix, foreignKey } }
|
||||
}),
|
||||
success: function(response) {
|
||||
if (response.errors) {
|
||||
console.error("[plugins] GraphQL errors:", response.errors);
|
||||
callback({ data: [], recordsTotal: 0, recordsFiltered: 0 });
|
||||
return;
|
||||
}
|
||||
const result = response.data[gqlField];
|
||||
callback({ data: result.entries, recordsTotal: result.dbCount, recordsFiltered: result.count }, result);
|
||||
},
|
||||
error: function() {
|
||||
callback({ data: [], recordsTotal: 0, recordsFiltered: 0 });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Fire a single batched GraphQL request to fetch the Objects dbCount for
|
||||
// every plugin and populate the sidebar badges immediately on page load.
|
||||
function prefetchPluginBadges() {
|
||||
const apiToken = getSetting("API_TOKEN");
|
||||
const apiBase = getApiBase();
|
||||
const mac = $("#txtMacFilter").val();
|
||||
const foreignKey = (mac && mac !== "--") ? mac : null;
|
||||
|
||||
// Build one aliased sub-query per visible plugin
|
||||
const prefixes = pluginDefinitions
|
||||
.filter(p => p.show_ui)
|
||||
.map(p => p.unique_prefix);
|
||||
|
||||
if (prefixes.length === 0) return;
|
||||
|
||||
// GraphQL aliases must be valid identifiers — prefixes already are (A-Z0-9_)
|
||||
const fkOpt = foreignKey ? `, foreignKey: "${foreignKey}"` : '';
|
||||
const fragments = prefixes.map(p => [
|
||||
`${p}_obj: pluginsObjects(options: {plugin: "${p}", page: 1, limit: 1${fkOpt}}) { dbCount }`,
|
||||
`${p}_evt: pluginsEvents(options: {plugin: "${p}", page: 1, limit: 1${fkOpt}}) { dbCount }`,
|
||||
`${p}_hist: pluginsHistory(options: {plugin: "${p}", page: 1, limit: 1${fkOpt}}) { dbCount }`,
|
||||
].join('\n ')).join('\n ');
|
||||
|
||||
const query = `query BadgeCounts {\n ${fragments}\n }`;
|
||||
|
||||
$.ajax({
|
||||
method: "POST",
|
||||
url: `${apiBase}/graphql`,
|
||||
headers: { "Authorization": `Bearer ${apiToken}`, "Content-Type": "application/json" },
|
||||
data: JSON.stringify({ query }),
|
||||
success: function(response) {
|
||||
if (response.errors) {
|
||||
console.error("[plugins] badge prefetch errors:", response.errors);
|
||||
return;
|
||||
}
|
||||
prefixes.forEach(p => {
|
||||
const obj = response.data[`${p}_obj`];
|
||||
const evt = response.data[`${p}_evt`];
|
||||
const hist = response.data[`${p}_hist`];
|
||||
if (obj) { $(`#badge_${p}`).text(obj.dbCount); $(`#objCount_${p}`).text(obj.dbCount); }
|
||||
if (evt) { $(`#evtCount_${p}`).text(evt.dbCount); }
|
||||
if (hist) { $(`#histCount_${p}`).text(hist.dbCount); }
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function generateTabs() {
|
||||
|
||||
@@ -315,17 +403,32 @@ function generateTabs() {
|
||||
// Sort pluginDefinitions by unique_prefix alphabetically
|
||||
pluginDefinitions.sort((a, b) => a.unique_prefix.localeCompare(b.unique_prefix));
|
||||
|
||||
assignActive = true;
|
||||
let assignActive = true;
|
||||
|
||||
// Iterate over the sorted pluginDefinitions to create tab headers and content
|
||||
pluginDefinitions.forEach(pluginObj => {
|
||||
if (pluginObj.show_ui) {
|
||||
stats = createTabContent(pluginObj, assignActive); // Create the content for each tab
|
||||
createTabContent(pluginObj, assignActive);
|
||||
createTabHeader(pluginObj, assignActive);
|
||||
assignActive = false;
|
||||
}
|
||||
});
|
||||
|
||||
if(stats.objectDataCount > 0)
|
||||
{
|
||||
createTabHeader(pluginObj, stats, assignActive); // Create the header for each tab
|
||||
assignActive = false; // only mark first with content active
|
||||
// Now that ALL DOM elements exist (both <a> headers and tab panes),
|
||||
// wire up DataTable initialization: immediate for the active tab,
|
||||
// deferred via shown.bs.tab for the rest.
|
||||
let firstVisible = true;
|
||||
pluginDefinitions.forEach(pluginObj => {
|
||||
if (pluginObj.show_ui) {
|
||||
const prefix = pluginObj.unique_prefix;
|
||||
const colDefinitions = getColumnDefinitions(pluginObj);
|
||||
if (firstVisible) {
|
||||
initializeDataTables(prefix, colDefinitions, pluginObj);
|
||||
firstVisible = false;
|
||||
} else {
|
||||
$(`a[href="#${prefix}"]`).one('shown.bs.tab', function() {
|
||||
initializeDataTables(prefix, colDefinitions, pluginObj);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -338,6 +441,9 @@ function generateTabs() {
|
||||
tabContainer: '#tabs-location'
|
||||
});
|
||||
|
||||
// Pre-fetch badge counts for every plugin in a single batched GraphQL call.
|
||||
prefetchPluginBadges();
|
||||
|
||||
hideSpinner()
|
||||
}
|
||||
|
||||
@@ -349,11 +455,11 @@ function resetTabs() {
|
||||
|
||||
// ---------------------------------------------------------------
|
||||
// left headers
|
||||
function createTabHeader(pluginObj, stats, assignActive) {
|
||||
function createTabHeader(pluginObj, assignActive) {
|
||||
const prefix = pluginObj.unique_prefix; // Get the unique prefix for the plugin
|
||||
|
||||
// Determine the active class for the first tab
|
||||
assignActive ? activeClass = "active" : activeClass = "";
|
||||
const activeClass = assignActive ? "active" : "";
|
||||
|
||||
// Append the tab header to the tabs location
|
||||
$('#tabs-location').append(`
|
||||
@@ -362,7 +468,7 @@ function createTabHeader(pluginObj, stats, assignActive) {
|
||||
${getString(`${prefix}_icon`)} ${getString(`${prefix}_display_name`)}
|
||||
|
||||
</a>
|
||||
${stats.objectDataCount > 0 ? `<div class="pluginBadgeWrap"><span title="" class="badge pluginBadge" >${stats.objectDataCount}</span></div>` : ""}
|
||||
<div class="pluginBadgeWrap"><span title="" class="badge pluginBadge" id="badge_${prefix}">…</span></div>
|
||||
</li>
|
||||
`);
|
||||
|
||||
@@ -374,19 +480,14 @@ function createTabContent(pluginObj, assignActive) {
|
||||
const prefix = pluginObj.unique_prefix; // Get the unique prefix for the plugin
|
||||
const colDefinitions = getColumnDefinitions(pluginObj); // Get column definitions for DataTables
|
||||
|
||||
// Get data for events, objects, and history related to the plugin
|
||||
const objectData = getObjectData(prefix, colDefinitions, pluginObj);
|
||||
const eventData = getEventData(prefix, colDefinitions, pluginObj);
|
||||
const historyData = getHistoryData(prefix, colDefinitions, pluginObj);
|
||||
|
||||
// Append the content structure for the plugin's tab to the content location
|
||||
$('#tabs-content-location').append(`
|
||||
<div id="${prefix}" class="tab-pane ${objectData.length > 0 && assignActive? 'active' : ''}">
|
||||
${generateTabNavigation(prefix, objectData.length, eventData.length, historyData.length)} <!-- Create tab navigation -->
|
||||
<div id="${prefix}" class="tab-pane ${assignActive ? 'active' : ''}">
|
||||
${generateTabNavigation(prefix)} <!-- Create tab navigation -->
|
||||
<div class="tab-content">
|
||||
${generateDataTable(prefix, 'Objects', objectData, colDefinitions)}
|
||||
${generateDataTable(prefix, 'Events', eventData, colDefinitions)}
|
||||
${generateDataTable(prefix, 'History', historyData, colDefinitions)}
|
||||
${generateDataTable(prefix, 'Objects', colDefinitions)}
|
||||
${generateDataTable(prefix, 'Events', colDefinitions)}
|
||||
${generateDataTable(prefix, 'History', colDefinitions)}
|
||||
</div>
|
||||
<div class='plugins-description'>
|
||||
${getString(`${prefix}_description`)} <!-- Display the plugin description -->
|
||||
@@ -395,14 +496,7 @@ function createTabContent(pluginObj, assignActive) {
|
||||
</div>
|
||||
`);
|
||||
|
||||
// Initialize DataTables for the respective sections
|
||||
initializeDataTables(prefix, objectData, eventData, historyData, colDefinitions);
|
||||
|
||||
return {
|
||||
"objectDataCount": objectData.length,
|
||||
"eventDataCount": eventData.length,
|
||||
"historyDataCount": historyData.length
|
||||
}
|
||||
// DataTable init is handled by generateTabs() after all DOM elements exist.
|
||||
}
|
||||
|
||||
function getColumnDefinitions(pluginObj) {
|
||||
@@ -410,53 +504,26 @@ function getColumnDefinitions(pluginObj) {
|
||||
return pluginObj["database_column_definitions"].filter(colDef => colDef.show);
|
||||
}
|
||||
|
||||
function getEventData(prefix, colDefinitions, pluginObj) {
|
||||
// Extract event data specific to the plugin and format it for DataTables
|
||||
return pluginUnprocessedEvents
|
||||
.filter(event => event.plugin === prefix && shouldBeShown(event, pluginObj)) // Filter events for the specific plugin
|
||||
.map(event => colDefinitions.map(colDef => event[colDef.column] || '')); // Map to the defined columns
|
||||
}
|
||||
|
||||
function getObjectData(prefix, colDefinitions, pluginObj) {
|
||||
// Extract object data specific to the plugin and format it for DataTables
|
||||
return pluginObjects
|
||||
.filter(object => object.plugin === prefix && shouldBeShown(object, pluginObj)) // Filter objects for the specific plugin
|
||||
.map(object => colDefinitions.map(colDef => getFormControl(colDef, object[colDef.column], object["index"], colDefinitions, object))); // Map to the defined columns
|
||||
}
|
||||
|
||||
function getHistoryData(prefix, colDefinitions, pluginObj) {
|
||||
|
||||
return pluginHistory
|
||||
.filter(history => history.plugin === prefix && shouldBeShown(history, pluginObj)) // First, filter based on the plugin prefix
|
||||
.sort((a, b) => b.index - a.index) // Then, sort by the Index field in descending order
|
||||
.slice(0, 50) // Limit the result to the first 50 entries
|
||||
.map(object =>
|
||||
colDefinitions.map(colDef =>
|
||||
getFormControl(colDef, object[colDef.column], object["index"], colDefinitions, object)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function generateTabNavigation(prefix, objectCount, eventCount, historyCount) {
|
||||
function generateTabNavigation(prefix) {
|
||||
// Create navigation tabs for Objects, Unprocessed Events, and History
|
||||
return `
|
||||
<div class="nav-tabs-custom" style="margin-bottom: 0px">
|
||||
<ul class="nav nav-tabs">
|
||||
<li class="active">
|
||||
<a href="#objectsTarget_${prefix}" data-toggle="tab"><i class="fa fa-cube"></i> ${getString('Plugins_Objects')} (${objectCount})</a>
|
||||
<a href="#objectsTarget_${prefix}" data-toggle="tab"><i class="fa fa-cube"></i> ${getString('Plugins_Objects')} (<span id="objCount_${prefix}">…</span>)</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#eventsTarget_${prefix}" data-toggle="tab"><i class="fa fa-bolt"></i> ${getString('Plugins_Unprocessed_Events')} (${eventCount})</a>
|
||||
<a href="#eventsTarget_${prefix}" data-toggle="tab"><i class="fa fa-bolt"></i> ${getString('Plugins_Unprocessed_Events')} (<span id="evtCount_${prefix}">…</span>)</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#historyTarget_${prefix}" data-toggle="tab"><i class="fa fa-clock"></i> ${getString('Plugins_History')} (${historyCount})</a>
|
||||
<a href="#historyTarget_${prefix}" data-toggle="tab"><i class="fa fa-clock"></i> ${getString('Plugins_History')} (<span id="histCount_${prefix}">…</span>)</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
function generateDataTable(prefix, tableType, data, colDefinitions) {
|
||||
function generateDataTable(prefix, tableType, colDefinitions) {
|
||||
// Generate HTML for a DataTable and associated buttons for a given table type
|
||||
const headersHtml = colDefinitions.map(colDef => `<th class="${colDef.css_classes}">${getString(`${prefix}_${colDef.column}_name`)}</th>`).join('');
|
||||
|
||||
@@ -473,34 +540,62 @@ function generateDataTable(prefix, tableType, data, colDefinitions) {
|
||||
`;
|
||||
}
|
||||
|
||||
function initializeDataTables(prefix, objectData, eventData, historyData, colDefinitions) {
|
||||
// Common settings for DataTables initialization
|
||||
const commonDataTableSettings = {
|
||||
orderable: false, // Disable ordering
|
||||
createdRow: function(row, data) {
|
||||
$(row).attr('data-my-index', data[0]); // Set data attribute for indexing
|
||||
function initializeDataTables(prefix, colDefinitions, pluginObj) {
|
||||
const mac = $("#txtMacFilter").val();
|
||||
const foreignKey = (mac && mac !== "--") ? mac : null;
|
||||
|
||||
const tableConfigs = [
|
||||
{ tableId: `objectsTable_${prefix}`, gqlField: 'pluginsObjects', countId: `objCount_${prefix}`, badgeId: `badge_${prefix}` },
|
||||
{ tableId: `eventsTable_${prefix}`, gqlField: 'pluginsEvents', countId: `evtCount_${prefix}`, badgeId: null },
|
||||
{ tableId: `historyTable_${prefix}`, gqlField: 'pluginsHistory', countId: `histCount_${prefix}`, badgeId: null },
|
||||
];
|
||||
|
||||
function buildDT(tableId, gqlField, countId, badgeId) {
|
||||
if ($.fn.DataTable.isDataTable(`#${tableId}`)) {
|
||||
return; // already initialized
|
||||
}
|
||||
};
|
||||
$(`#${tableId}`).DataTable({
|
||||
processing: true,
|
||||
serverSide: true,
|
||||
paging: true,
|
||||
searching: true,
|
||||
ordering: false,
|
||||
pageLength: 25,
|
||||
lengthMenu: [[10, 25, 50, 100], [10, 25, 50, 100]],
|
||||
createdRow: function(row, data) {
|
||||
$(row).attr('data-my-index', data.index);
|
||||
},
|
||||
ajax: function(dtRequest, callback) {
|
||||
postPluginGraphQL(gqlField, prefix, foreignKey, dtRequest, function(dtResponse, result) {
|
||||
if (result) {
|
||||
$(`#${countId}`).text(result.count);
|
||||
if (badgeId) $(`#${badgeId}`).text(result.dbCount);
|
||||
}
|
||||
callback(dtResponse);
|
||||
});
|
||||
},
|
||||
columns: colDefinitions.map(colDef => ({
|
||||
data: colDef.column,
|
||||
title: getString(`${prefix}_${colDef.column}_name`),
|
||||
className: colDef.css_classes || '',
|
||||
createdCell: function(td, cellData, rowData) {
|
||||
$(td).html(getFormControl(colDef, cellData, rowData.index));
|
||||
}
|
||||
}))
|
||||
});
|
||||
}
|
||||
|
||||
// Initialize DataTable for Objects
|
||||
$(`#objectsTable_${prefix}`).DataTable({
|
||||
data: objectData,
|
||||
columns: colDefinitions.map(colDef => ({ title: getString(`${prefix}_${colDef.column}_name`) })), // Column titles
|
||||
...commonDataTableSettings // Spread common settings
|
||||
// Initialize the Objects table immediately (it is the active/visible sub-tab).
|
||||
// Defer Events and History tables until their sub-tab is first shown.
|
||||
const [objCfg, evtCfg, histCfg] = tableConfigs;
|
||||
buildDT(objCfg.tableId, objCfg.gqlField, objCfg.countId, objCfg.badgeId);
|
||||
|
||||
$(`a[href="#eventsTarget_${prefix}"]`).one('shown.bs.tab', function() {
|
||||
buildDT(evtCfg.tableId, evtCfg.gqlField, evtCfg.countId, evtCfg.badgeId);
|
||||
});
|
||||
|
||||
// Initialize DataTable for Unprocessed Events
|
||||
$(`#eventsTable_${prefix}`).DataTable({
|
||||
data: eventData,
|
||||
columns: colDefinitions.map(colDef => ({ title: getString(`${prefix}_${colDef.column}_name`) })), // Column titles
|
||||
...commonDataTableSettings // Spread common settings
|
||||
});
|
||||
|
||||
// Initialize DataTable for History
|
||||
$(`#historyTable_${prefix}`).DataTable({
|
||||
data: historyData,
|
||||
columns: colDefinitions.map(colDef => ({ title: getString(`${prefix}_${colDef.column}_name`) })), // Column titles
|
||||
...commonDataTableSettings // Spread common settings
|
||||
$(`a[href="#historyTarget_${prefix}"]`).one('shown.bs.tab', function() {
|
||||
buildDT(histCfg.tableId, histCfg.gqlField, histCfg.countId, histCfg.badgeId);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user