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:
parent
fb22a9146e
commit
b60ceba2a6
@ -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,
|
||||
|
||||
@ -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();
|
||||
});
|
||||
},
|
||||
|
||||
@ -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"}'
|
||||
;;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user