Skip to content

Commit 448e17c

Browse files
authored
Merge pull request #1577 from netalertx/next_release
feat(plugins): Optimize badge fetching by using lightweight JSON inst…
2 parents 9b71662 + 4daead1 commit 448e17c

3 files changed

Lines changed: 74 additions & 33 deletions

File tree

front/pluginsCore.php

Lines changed: 60 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -348,51 +348,78 @@ function postPluginGraphQL(gqlField, prefix, foreignKey, dtRequest, callback) {
348348
});
349349
}
350350

351-
// Fire a single batched GraphQL request to fetch the Objects dbCount for
352-
// every plugin and populate the sidebar badges immediately on page load.
353-
function prefetchPluginBadges() {
354-
const apiToken = getSetting("API_TOKEN");
355-
const apiBase = getApiBase();
351+
// Fetch badge counts for every plugin and populate sidebar + sub-tab counters.
352+
// Fast path: static JSON (~1KB) when no MAC filter is active.
353+
// Filtered path: batched GraphQL aliases when a foreignKey (MAC) is set.
354+
async function prefetchPluginBadges() {
356355
const mac = $("#txtMacFilter").val();
357356
const foreignKey = (mac && mac !== "--") ? mac : null;
358357

359-
// Build one aliased sub-query per visible plugin
360358
const prefixes = pluginDefinitions
361359
.filter(p => p.show_ui)
362360
.map(p => p.unique_prefix);
363361

364362
if (prefixes.length === 0) return;
365363

366-
// GraphQL aliases must be valid identifiers — prefixes already are (A-Z0-9_)
367-
const fkOpt = foreignKey ? `, foreignKey: "${foreignKey}"` : '';
368-
const fragments = prefixes.map(p => [
369-
`${p}_obj: pluginsObjects(options: {plugin: "${p}", page: 1, limit: 1${fkOpt}}) { dbCount }`,
370-
`${p}_evt: pluginsEvents(options: {plugin: "${p}", page: 1, limit: 1${fkOpt}}) { dbCount }`,
371-
`${p}_hist: pluginsHistory(options: {plugin: "${p}", page: 1, limit: 1${fkOpt}}) { dbCount }`,
372-
].join('\n ')).join('\n ');
373-
374-
const query = `query BadgeCounts {\n ${fragments}\n }`;
375-
376-
$.ajax({
377-
method: "POST",
378-
url: `${apiBase}/graphql`,
379-
headers: { "Authorization": `Bearer ${apiToken}`, "Content-Type": "application/json" },
380-
data: JSON.stringify({ query }),
381-
success: function(response) {
382-
if (response.errors) {
383-
console.error("[plugins] badge prefetch errors:", response.errors);
384-
return;
364+
try {
365+
let counts = {}; // { PREFIX: { objects: N, events: N, history: N } }
366+
367+
if (!foreignKey) {
368+
// ---- FAST PATH: lightweight pre-computed JSON ----
369+
const stats = await fetchJson('table_plugins_stats.json');
370+
for (const row of stats.data) {
371+
const p = row.tableName; // 'objects' | 'events' | 'history'
372+
const plugin = row.plugin;
373+
if (!counts[plugin]) counts[plugin] = { objects: 0, events: 0, history: 0 };
374+
counts[plugin][p] = row.cnt;
385375
}
386-
prefixes.forEach(p => {
387-
const obj = response.data[`${p}_obj`];
388-
const evt = response.data[`${p}_evt`];
389-
const hist = response.data[`${p}_hist`];
390-
if (obj) { $(`#badge_${p}`).text(obj.dbCount); $(`#objCount_${p}`).text(obj.dbCount); }
391-
if (evt) { $(`#evtCount_${p}`).text(evt.dbCount); }
392-
if (hist) { $(`#histCount_${p}`).text(hist.dbCount); }
376+
} else {
377+
// ---- FILTERED PATH: GraphQL with foreignKey ----
378+
const apiToken = getSetting("API_TOKEN");
379+
const apiBase = getApiBase();
380+
const fkOpt = `, foreignKey: "${foreignKey}"`;
381+
const fragments = prefixes.map(p => [
382+
`${p}_obj: pluginsObjects(options: {plugin: "${p}", page: 1, limit: 1${fkOpt}}) { dbCount }`,
383+
`${p}_evt: pluginsEvents(options: {plugin: "${p}", page: 1, limit: 1${fkOpt}}) { dbCount }`,
384+
`${p}_hist: pluginsHistory(options: {plugin: "${p}", page: 1, limit: 1${fkOpt}}) { dbCount }`,
385+
].join('\n ')).join('\n ');
386+
387+
const query = `query BadgeCounts {\n ${fragments}\n }`;
388+
const response = await $.ajax({
389+
method: "POST",
390+
url: `${apiBase}/graphql`,
391+
headers: { "Authorization": `Bearer ${apiToken}`, "Content-Type": "application/json" },
392+
data: JSON.stringify({ query }),
393393
});
394+
if (response.errors) { console.error("[plugins] badge GQL errors:", response.errors); return; }
395+
for (const p of prefixes) {
396+
counts[p] = {
397+
objects: response.data[`${p}_obj`]?.dbCount ?? 0,
398+
events: response.data[`${p}_evt`]?.dbCount ?? 0,
399+
history: response.data[`${p}_hist`]?.dbCount ?? 0,
400+
};
401+
}
394402
}
395-
});
403+
404+
// Update DOM
405+
for (const [prefix, c] of Object.entries(counts)) {
406+
$(`#badge_${prefix}`).text(c.objects);
407+
$(`#objCount_${prefix}`).text(c.objects);
408+
$(`#evtCount_${prefix}`).text(c.events);
409+
$(`#histCount_${prefix}`).text(c.history);
410+
}
411+
// Zero out plugins with no rows in any table
412+
prefixes.forEach(prefix => {
413+
if (!counts[prefix]) {
414+
$(`#badge_${prefix}`).text(0);
415+
$(`#objCount_${prefix}`).text(0);
416+
$(`#evtCount_${prefix}`).text(0);
417+
$(`#histCount_${prefix}`).text(0);
418+
}
419+
});
420+
} catch (err) {
421+
console.error('[plugins] badge prefetch failed:', err);
422+
}
396423
}
397424

398425
function generateTabs() {

server/api.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
sql_plugins_events,
1717
sql_plugins_history,
1818
sql_plugins_objects,
19+
sql_plugins_stats,
1920
sql_language_strings,
2021
sql_notifications_all,
2122
sql_online_history,
@@ -66,6 +67,7 @@ def update_api(
6667
["plugins_events", sql_plugins_events],
6768
["plugins_history", sql_plugins_history],
6869
["plugins_objects", sql_plugins_objects],
70+
["plugins_stats", sql_plugins_stats],
6971
["plugins_language_strings", sql_language_strings],
7072
["notifications", sql_notifications_all],
7173
["online_history", sql_online_history],
@@ -74,6 +76,13 @@ def update_api(
7476
["custom_endpoint", conf.API_CUSTOM_SQL],
7577
]
7678

79+
# plugins_stats is derived from plugins_objects/events/history —
80+
# ensure it is refreshed when any of its sources are partially updated.
81+
_STATS_SOURCES = {"plugins_objects", "plugins_events", "plugins_history"}
82+
if updateOnlyDataSources and _STATS_SOURCES & set(updateOnlyDataSources):
83+
if "plugins_stats" not in updateOnlyDataSources:
84+
updateOnlyDataSources = list(updateOnlyDataSources) + ["plugins_stats"]
85+
7786
# Save selected database tables
7887
for dsSQL in dataSourcesSQLs:
7988
if not updateOnlyDataSources or dsSQL[0] in updateOnlyDataSources:

server/const.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,11 @@
120120
sql_events_all = "SELECT rowid, * FROM Events ORDER BY eveDateTime DESC"
121121
sql_settings = "SELECT * FROM Settings"
122122
sql_plugins_objects = "SELECT * FROM Plugins_Objects"
123+
sql_plugins_stats = """SELECT 'objects' AS tableName, plugin, COUNT(*) AS cnt FROM Plugins_Objects GROUP BY plugin
124+
UNION ALL
125+
SELECT 'events', plugin, COUNT(*) FROM Plugins_Events GROUP BY plugin
126+
UNION ALL
127+
SELECT 'history', plugin, COUNT(*) FROM Plugins_History GROUP BY plugin"""
123128
sql_language_strings = "SELECT * FROM Plugins_Language_Strings"
124129
sql_notifications_all = "SELECT * FROM Notifications"
125130
sql_online_history = "SELECT * FROM Online_History"

0 commit comments

Comments
 (0)