diff --git a/package/secubox/luci-app-crowdsec-dashboard/htdocs/luci-static/resources/crowdsec-dashboard/api.js b/package/secubox/luci-app-crowdsec-dashboard/htdocs/luci-static/resources/crowdsec-dashboard/api.js index fdde5b9a..d3b598f6 100644 --- a/package/secubox/luci-app-crowdsec-dashboard/htdocs/luci-static/resources/crowdsec-dashboard/api.js +++ b/package/secubox/luci-app-crowdsec-dashboard/htdocs/luci-static/resources/crowdsec-dashboard/api.js @@ -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, diff --git a/package/secubox/luci-app-crowdsec-dashboard/htdocs/luci-static/resources/view/crowdsec-dashboard/overview.js b/package/secubox/luci-app-crowdsec-dashboard/htdocs/luci-static/resources/view/crowdsec-dashboard/overview.js index bbb8d850..c0403f4b 100644 --- a/package/secubox/luci-app-crowdsec-dashboard/htdocs/luci-static/resources/view/crowdsec-dashboard/overview.js +++ b/package/secubox/luci-app-crowdsec-dashboard/htdocs/luci-static/resources/view/crowdsec-dashboard/overview.js @@ -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(); }); }, diff --git a/package/secubox/luci-app-crowdsec-dashboard/root/usr/libexec/rpcd/luci.crowdsec-dashboard b/package/secubox/luci-app-crowdsec-dashboard/root/usr/libexec/rpcd/luci.crowdsec-dashboard index 094b8a30..9e12d892 100755 --- a/package/secubox/luci-app-crowdsec-dashboard/root/usr/libexec/rpcd/luci.crowdsec-dashboard +++ b/package/secubox/luci-app-crowdsec-dashboard/root/usr/libexec/rpcd/luci.crowdsec-dashboard @@ -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"}' ;;