diff --git a/front/pluginsCore.php b/front/pluginsCore.php index c76535b0..1fa8ab13 100755 --- a/front/pluginsCore.php +++ b/front/pluginsCore.php @@ -348,51 +348,78 @@ function postPluginGraphQL(gqlField, prefix, foreignKey, dtRequest, callback) { }); } -// 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(); +// Fetch badge counts for every plugin and populate sidebar + sub-tab counters. +// Fast path: static JSON (~1KB) when no MAC filter is active. +// Filtered path: batched GraphQL aliases when a foreignKey (MAC) is set. +async function prefetchPluginBadges() { 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 '); + try { + let counts = {}; // { PREFIX: { objects: N, events: N, history: 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; + if (!foreignKey) { + // ---- FAST PATH: lightweight pre-computed JSON ---- + const stats = await fetchJson('table_plugins_stats.json'); + for (const row of stats.data) { + const p = row.tableName; // 'objects' | 'events' | 'history' + const plugin = row.plugin; + if (!counts[plugin]) counts[plugin] = { objects: 0, events: 0, history: 0 }; + counts[plugin][p] = row.cnt; } - 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); } + } else { + // ---- FILTERED PATH: GraphQL with foreignKey ---- + const apiToken = getSetting("API_TOKEN"); + const apiBase = getApiBase(); + const fkOpt = `, 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 }`; + const response = await $.ajax({ + method: "POST", + url: `${apiBase}/graphql`, + headers: { "Authorization": `Bearer ${apiToken}`, "Content-Type": "application/json" }, + data: JSON.stringify({ query }), }); + if (response.errors) { console.error("[plugins] badge GQL errors:", response.errors); return; } + for (const p of prefixes) { + counts[p] = { + objects: response.data[`${p}_obj`]?.dbCount ?? 0, + events: response.data[`${p}_evt`]?.dbCount ?? 0, + history: response.data[`${p}_hist`]?.dbCount ?? 0, + }; + } } - }); + + // Update DOM + for (const [prefix, c] of Object.entries(counts)) { + $(`#badge_${prefix}`).text(c.objects); + $(`#objCount_${prefix}`).text(c.objects); + $(`#evtCount_${prefix}`).text(c.events); + $(`#histCount_${prefix}`).text(c.history); + } + // Zero out plugins with no rows in any table + prefixes.forEach(prefix => { + if (!counts[prefix]) { + $(`#badge_${prefix}`).text(0); + $(`#objCount_${prefix}`).text(0); + $(`#evtCount_${prefix}`).text(0); + $(`#histCount_${prefix}`).text(0); + } + }); + } catch (err) { + console.error('[plugins] badge prefetch failed:', err); + } } function generateTabs() { diff --git a/server/api.py b/server/api.py index fd775f47..11834bc4 100755 --- a/server/api.py +++ b/server/api.py @@ -16,6 +16,7 @@ from const import ( sql_plugins_events, sql_plugins_history, sql_plugins_objects, + sql_plugins_stats, sql_language_strings, sql_notifications_all, sql_online_history, @@ -66,6 +67,7 @@ def update_api( ["plugins_events", sql_plugins_events], ["plugins_history", sql_plugins_history], ["plugins_objects", sql_plugins_objects], + ["plugins_stats", sql_plugins_stats], ["plugins_language_strings", sql_language_strings], ["notifications", sql_notifications_all], ["online_history", sql_online_history], @@ -74,6 +76,13 @@ def update_api( ["custom_endpoint", conf.API_CUSTOM_SQL], ] + # plugins_stats is derived from plugins_objects/events/history — + # ensure it is refreshed when any of its sources are partially updated. + _STATS_SOURCES = {"plugins_objects", "plugins_events", "plugins_history"} + if updateOnlyDataSources and _STATS_SOURCES & set(updateOnlyDataSources): + if "plugins_stats" not in updateOnlyDataSources: + updateOnlyDataSources = list(updateOnlyDataSources) + ["plugins_stats"] + # Save selected database tables for dsSQL in dataSourcesSQLs: if not updateOnlyDataSources or dsSQL[0] in updateOnlyDataSources: diff --git a/server/const.py b/server/const.py index 97faa00b..1dc1e83b 100755 --- a/server/const.py +++ b/server/const.py @@ -120,6 +120,11 @@ sql_events_pending_alert = "SELECT * FROM Events where evePendingAlertEmail is sql_events_all = "SELECT rowid, * FROM Events ORDER BY eveDateTime DESC" sql_settings = "SELECT * FROM Settings" sql_plugins_objects = "SELECT * FROM Plugins_Objects" +sql_plugins_stats = """SELECT 'objects' AS tableName, plugin, COUNT(*) AS cnt FROM Plugins_Objects GROUP BY plugin + UNION ALL + SELECT 'events', plugin, COUNT(*) FROM Plugins_Events GROUP BY plugin + UNION ALL + SELECT 'history', plugin, COUNT(*) FROM Plugins_History GROUP BY plugin""" sql_language_strings = "SELECT * FROM Plugins_Language_Strings" sql_notifications_all = "SELECT * FROM Notifications" sql_online_history = "SELECT * FROM Online_History"