perf(crowdsec-dashboard): Add consolidated get_overview API call

Consolidate multiple dashboard API calls into a single get_overview RPC
method to reduce network overhead and improve page load performance.
The frontend now transforms the consolidated response to maintain
compatibility with existing view logic. Also increases poll interval
from 30s to 60s.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
CyberMind-FR 2026-01-14 10:18:41 +01:00
parent fb22a9146e
commit b60ceba2a6
3 changed files with 201 additions and 19 deletions

View File

@ -59,6 +59,12 @@ var callStats = rpc.declare({
expect: { }
});
var callGetOverview = rpc.declare({
object: 'luci.crowdsec-dashboard',
method: 'get_overview',
expect: { }
});
var callSecuboxLogs = rpc.declare({
object: 'luci.crowdsec-dashboard',
method: 'secubox_logs',
@ -395,6 +401,7 @@ return baseclass.extend({
getMachines: callMachines,
getHub: callHub,
getStats: callStats,
getOverview: callGetOverview,
getSecuboxLogs: callSecuboxLogs,
collectDebugSnapshot: callCollectDebug,
addBan: callBan,

View File

@ -32,15 +32,85 @@ return view.extend({
// Load API
this.csApi = api;
// Use consolidated API call + secondary calls for extended data
return Promise.all([
this.csApi.getDashboardData(),
this.csApi.getSecuboxLogs(),
this.csApi.getHealthCheck().catch(function() { return {}; }),
this.csApi.getOverview().catch(function() { return {}; }),
this.csApi.getCapiMetrics().catch(function() { return {}; }),
this.csApi.getCollections().catch(function() { return { collections: [] }; }),
this.csApi.getNftablesStats().catch(function() { return {}; }),
this.csApi.getHub().catch(function() { return {}; })
]);
]).then(this.transformOverviewData.bind(this));
},
// Transform getOverview response to expected data structure
transformOverviewData: function(results) {
var overview = results[0] || {};
var capiMetrics = results[1] || {};
var collectionsData = results[2] || {};
var nftablesStats = results[3] || {};
var hubData = results[4] || {};
// Parse raw JSON strings for scenarios and countries
var topScenarios = [];
var topCountries = [];
try {
if (overview.top_scenarios_raw) {
topScenarios = JSON.parse(overview.top_scenarios_raw);
}
} catch(e) { topScenarios = []; }
try {
if (overview.top_countries_raw) {
topCountries = JSON.parse(overview.top_countries_raw);
}
} catch(e) { topCountries = []; }
// Build compatible data structure
var dashboardData = {
status: {
crowdsec: overview.crowdsec || 'unknown',
bouncer: overview.bouncer || 'unknown',
version: overview.version || 'unknown'
},
stats: {
total_decisions: overview.total_decisions || 0,
alerts_today: overview.alerts_24h || 0,
alerts_week: overview.alerts_24h || 0,
scenarios_triggered: topScenarios.length,
top_countries: topCountries.map(function(c) {
return { country: c.country, count: c.count };
})
},
decisions: overview.decisions || [],
alerts: overview.alerts || [],
error: null
};
var logsData = {
entries: overview.logs || []
};
var healthCheck = {
crowdsec_running: overview.crowdsec === 'running',
lapi_status: overview.lapi_status || 'unavailable',
capi_status: overview.capi_enrolled ? 'connected' : 'disconnected',
capi_enrolled: overview.capi_enrolled || false,
capi_subscription: 'COMMUNITY',
sharing_signals: overview.capi_enrolled || false,
pulling_blocklist: overview.capi_enrolled || false,
version: overview.version || 'N/A',
decisions_count: overview.total_decisions || 0
};
// Return array matching expected payload structure
return [
dashboardData,
logsData,
healthCheck,
capiMetrics,
collectionsData,
nftablesStats,
hubData
];
},
renderHeader: function(status) {
@ -535,32 +605,32 @@ return view.extend({
wrapper.appendChild(view);
// Setup polling for auto-refresh (every 30 seconds)
// Setup polling for auto-refresh (every 60 seconds)
poll.add(function() {
return self.refreshDashboard();
}, 30);
}, 60);
return wrapper;
},
refreshDashboard: function() {
refreshDashboard: function() {
var self = this;
// Use consolidated API call + secondary calls for extended data
return Promise.all([
self.csApi.getDashboardData(),
self.csApi.getSecuboxLogs(),
self.csApi.getHealthCheck().catch(function() { return {}; }),
self.csApi.getOverview().catch(function() { return {}; }),
self.csApi.getCapiMetrics().catch(function() { return {}; }),
self.csApi.getCollections().catch(function() { return { collections: [] }; }),
self.csApi.getNftablesStats().catch(function() { return {}; }),
self.csApi.getHub().catch(function() { return {}; })
]).then(function(results) {
self.data = results[0];
self.logs = (results[1] && results[1].entries) || [];
self.healthCheck = results[2] || {};
self.capiMetrics = results[3] || {};
self.collections = (results[4] && results[4].collections) || [];
self.nftablesStats = results[5] || {};
self.hubData = results[6] || {};
var transformed = self.transformOverviewData(results);
self.data = transformed[0];
self.logs = (transformed[1] && transformed[1].entries) || [];
self.healthCheck = transformed[2] || {};
self.capiMetrics = transformed[3] || {};
self.collections = (transformed[4] && transformed[4].collections) || [];
self.nftablesStats = transformed[5] || {};
self.hubData = transformed[6] || {};
self.updateView();
});
},

View File

@ -2077,10 +2077,112 @@ save_settings() {
json_dump
}
# Consolidated overview data for dashboard - single API call optimization
get_overview() {
json_init
# Service status (fast - just process checks)
local cs_running=0
pgrep crowdsec >/dev/null 2>&1 && cs_running=1
json_add_string "crowdsec" "$([ "$cs_running" = "1" ] && echo running || echo stopped)"
local bouncer_running=0
pgrep -f "crowdsec-firewall-bouncer" >/dev/null 2>&1 && bouncer_running=1
json_add_string "bouncer" "$([ "$bouncer_running" = "1" ] && echo running || echo stopped)"
# Version
local version=""
[ -x "$CSCLI" ] && version=$("$CSCLI" version 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1)
json_add_string "version" "${version:-unknown}"
# Quick stats
local decisions_count=0
local alerts_count=0
local bouncers_count=0
if [ "$cs_running" = "1" ] && [ -x "$CSCLI" ]; then
decisions_count=$(run_cscli decisions list -o json 2>/dev/null | jsonfilter -e '@[*]' 2>/dev/null | wc -l)
alerts_count=$(run_cscli alerts list -o json --since 24h --limit 100 2>/dev/null | jsonfilter -e '@[*]' 2>/dev/null | wc -l)
bouncers_count=$(run_cscli bouncers list -o json 2>/dev/null | jsonfilter -e '@[*]' 2>/dev/null | wc -l)
fi
json_add_int "total_decisions" "${decisions_count:-0}"
json_add_int "alerts_24h" "${alerts_count:-0}"
json_add_int "bouncers" "${bouncers_count:-0}"
# Top scenarios (from cached/limited alerts)
local scenarios=""
if [ "$cs_running" = "1" ]; then
scenarios=$(run_cscli alerts list -o json --limit 100 2>/dev/null | \
jsonfilter -e '@[*].scenario' 2>/dev/null | \
sort | uniq -c | sort -rn | head -5 | \
awk '{print "{\"scenario\":\"" $2 "\",\"count\":" $1 "}"}' | \
tr '\n' ',' | sed 's/,$//')
fi
json_add_string "top_scenarios_raw" "[$scenarios]"
# Top countries (from alerts with GeoIP)
local countries=""
if [ "$cs_running" = "1" ]; then
countries=$(run_cscli alerts list -o json --limit 200 2>/dev/null | \
jsonfilter -e '@[*].source.cn' 2>/dev/null | \
grep -v '^$' | sort | uniq -c | sort -rn | head -10 | \
awk '{print "{\"country\":\"" $2 "\",\"count\":" $1 "}"}' | \
tr '\n' ',' | sed 's/,$//')
fi
json_add_string "top_countries_raw" "[$countries]"
# Recent decisions (limited to 10 for display)
json_add_array "decisions"
if [ "$cs_running" = "1" ]; then
run_cscli decisions list -o json 2>/dev/null | \
jsonfilter -e '@[0]' -e '@[1]' -e '@[2]' -e '@[3]' -e '@[4]' \
-e '@[5]' -e '@[6]' -e '@[7]' -e '@[8]' -e '@[9]' 2>/dev/null | \
while IFS= read -r line; do
[ -n "$line" ] && [ "$line" != "null" ] && json_add_string "" "$line"
done
fi
json_close_array
# Recent alerts (limited to 8)
json_add_array "alerts"
if [ "$cs_running" = "1" ]; then
run_cscli alerts list -o json --limit 8 2>/dev/null | \
jsonfilter -e '@[*]' 2>/dev/null | head -8 | \
while IFS= read -r line; do
[ -n "$line" ] && json_add_string "" "$line"
done
fi
json_close_array
# CrowdSec logs (last 30 lines)
json_add_array "logs"
if [ -f /var/log/crowdsec.log ]; then
tail -n 30 /var/log/crowdsec.log 2>/dev/null | while IFS= read -r line; do
json_add_string "" "$line"
done
fi
json_close_array
# LAPI status (quick check)
local lapi_ok=0
if [ "$cs_running" = "1" ] && grep -qi ":1F90 " /proc/net/tcp 2>/dev/null; then
lapi_ok=1
fi
json_add_string "lapi_status" "$([ "$lapi_ok" = "1" ] && echo available || echo unavailable)"
# CAPI status (from config check, not live call)
local capi_enrolled=0
[ -f /etc/crowdsec/online_api_credentials.yaml ] && capi_enrolled=1
json_add_boolean "capi_enrolled" "$capi_enrolled"
json_dump
}
# Main dispatcher
case "$1" in
list)
echo '{"decisions":{},"alerts":{"limit":"number"},"metrics":{},"bouncers":{},"machines":{},"hub":{},"status":{},"ban":{"ip":"string","duration":"string","reason":"string"},"unban":{"ip":"string"},"stats":{},"secubox_logs":{},"collect_debug":{},"waf_status":{},"metrics_config":{},"configure_metrics":{"enable":"string"},"collections":{},"install_collection":{"collection":"string"},"remove_collection":{"collection":"string"},"update_hub":{},"register_bouncer":{"bouncer_name":"string"},"delete_bouncer":{"bouncer_name":"string"},"firewall_bouncer_status":{},"control_firewall_bouncer":{"action":"string"},"firewall_bouncer_config":{},"update_firewall_bouncer_config":{"key":"string","value":"string"},"nftables_stats":{},"check_wizard_needed":{},"wizard_state":{},"repair_lapi":{},"repair_capi":{},"reset_wizard":{},"console_status":{},"console_enroll":{"key":"string","name":"string"},"console_disable":{},"service_control":{"action":"string"},"configure_acquisition":{"syslog_enabled":"string","firewall_enabled":"string","ssh_enabled":"string","http_enabled":"string","syslog_path":"string"},"acquisition_config":{},"acquisition_metrics":{},"health_check":{},"capi_metrics":{},"hub_available":{},"install_hub_item":{"item_type":"string","item_name":"string"},"remove_hub_item":{"item_type":"string","item_name":"string"},"get_settings":{},"save_settings":{"enrollment_key":"string","machine_name":"string","auto_enroll":"string"}}'
echo '{"get_overview":{},"decisions":{},"alerts":{"limit":"number"},"metrics":{},"bouncers":{},"machines":{},"hub":{},"status":{},"ban":{"ip":"string","duration":"string","reason":"string"},"unban":{"ip":"string"},"stats":{},"secubox_logs":{},"collect_debug":{},"waf_status":{},"metrics_config":{},"configure_metrics":{"enable":"string"},"collections":{},"install_collection":{"collection":"string"},"remove_collection":{"collection":"string"},"update_hub":{},"register_bouncer":{"bouncer_name":"string"},"delete_bouncer":{"bouncer_name":"string"},"firewall_bouncer_status":{},"control_firewall_bouncer":{"action":"string"},"firewall_bouncer_config":{},"update_firewall_bouncer_config":{"key":"string","value":"string"},"nftables_stats":{},"check_wizard_needed":{},"wizard_state":{},"repair_lapi":{},"repair_capi":{},"reset_wizard":{},"console_status":{},"console_enroll":{"key":"string","name":"string"},"console_disable":{},"service_control":{"action":"string"},"configure_acquisition":{"syslog_enabled":"string","firewall_enabled":"string","ssh_enabled":"string","http_enabled":"string","syslog_path":"string"},"acquisition_config":{},"acquisition_metrics":{},"health_check":{},"capi_metrics":{},"hub_available":{},"install_hub_item":{"item_type":"string","item_name":"string"},"remove_hub_item":{"item_type":"string","item_name":"string"},"get_settings":{},"save_settings":{"enrollment_key":"string","machine_name":"string","auto_enroll":"string"}}'
;;
call)
case "$2" in
@ -2263,6 +2365,9 @@ case "$1" in
auto_enroll=$(echo "$input" | jsonfilter -e '@.auto_enroll' 2>/dev/null)
save_settings "$enrollment_key" "$machine_name" "$auto_enroll"
;;
get_overview)
get_overview
;;
*)
echo '{"error": "Unknown method"}'
;;