diff --git a/package/secubox/luci-app-secubox/htdocs/luci-static/resources/view/status/secubox-metrics.js b/package/secubox/luci-app-secubox/htdocs/luci-static/resources/view/status/secubox-metrics.js index 3a2d4949..37ade95e 100644 --- a/package/secubox/luci-app-secubox/htdocs/luci-static/resources/view/status/secubox-metrics.js +++ b/package/secubox/luci-app-secubox/htdocs/luci-static/resources/view/status/secubox-metrics.js @@ -15,17 +15,25 @@ var callGetVisitStats = rpc.declare({ expect: { } }); +var callGetActiveSessions = rpc.declare({ + object: 'luci.secubox', + method: 'get_active_sessions', + expect: { } +}); + return view.extend({ load: function() { return Promise.all([ callGetSystemOverview(), - callGetVisitStats().catch(function() { return {}; }) + callGetVisitStats().catch(function() { return {}; }), + callGetActiveSessions().catch(function() { return {}; }) ]); }, render: function(results) { var overview = results[0] || {}; var visitStats = results[1] || {}; + var sessions = results[2] || {}; var sys = overview.system || {}; var net = overview.network || {}; var svc = overview.services || {}; @@ -33,6 +41,9 @@ return view.extend({ var byCountry = visitStats.by_country || []; var byHost = visitStats.by_host || []; var botsHumans = visitStats.bots_vs_humans || {}; + var sessionCounts = sessions.counts || {}; + var recentVisitors = sessions.recent_visitors || []; + var topEndpoints = sessions.top_endpoints || []; var style = E('style', {}, ` .metrics-container { @@ -70,6 +81,10 @@ return view.extend({ background: rgba(0,255,136,0.1); border-color: rgba(0,255,136,0.4); } + .metrics-section.sessions { + background: rgba(255,200,0,0.1); + border-color: rgba(255,200,0,0.4); + } .metrics-section h3 { margin: 0 0 12px 0; font-size: 14px; @@ -85,6 +100,10 @@ return view.extend({ color: #00ff88; border-color: rgba(0,255,136,0.4); } + .metrics-section.sessions h3 { + color: #ffc800; + border-color: rgba(255,200,0,0.4); + } .metrics-row { display: flex; justify-content: space-between; @@ -104,6 +123,9 @@ return view.extend({ .metrics-section.traffic .metrics-value { color: #00ff88; } + .metrics-section.sessions .metrics-value { + color: #ffc800; + } .metrics-bar { height: 8px; background: rgba(0,255,255,0.2); @@ -132,16 +154,22 @@ return view.extend({ .country-tag .flag { margin-right: 4px; } - .host-list { + .host-list, .visitor-list, .endpoint-list { font-size: 11px; margin-top: 8px; } - .host-item { + .host-item, .visitor-item, .endpoint-item { display: flex; justify-content: space-between; padding: 3px 0; border-bottom: 1px solid rgba(0,255,136,0.1); } + .visitor-item { + border-color: rgba(255,200,0,0.1); + } + .endpoint-item { + border-color: rgba(255,200,0,0.1); + } `); // Build country tags @@ -164,6 +192,26 @@ return view.extend({ ]); }); + // Build recent visitors list + var visitorItems = recentVisitors.slice(0, 6).map(function(v) { + var flag = v.country && v.country.length === 2 ? + String.fromCodePoint(...[...v.country.toUpperCase()].map(c => 0x1F1E6 - 65 + c.charCodeAt(0))) : ''; + return E('div', { 'class': 'visitor-item' }, [ + E('span', {}, (v.ip || '-').substring(0, 15)), + E('span', { 'class': 'metrics-value' }, flag + ' ' + (v.country || '??')) + ]); + }); + + // Build top endpoints list + var endpointItems = topEndpoints.slice(0, 5).map(function(e) { + var path = e.path || '-'; + if (path.length > 28) path = path.substring(0, 25) + '...'; + return E('div', { 'class': 'endpoint-item' }, [ + E('span', {}, path), + E('span', { 'class': 'metrics-value' }, String(e.count || 0)) + ]); + }); + var container = E('div', { 'class': 'metrics-container' }, [ E('div', { 'class': 'metrics-header' }, 'SECUBOX SYSTEM METRICS'), E('div', { 'class': 'metrics-grid' }, [ @@ -211,7 +259,40 @@ return view.extend({ ]) ]), - // Web Traffic - NEW + // Active Sessions - NEW + E('div', { 'class': 'metrics-section sessions' }, [ + E('h3', {}, 'ACTIVE SESSIONS'), + E('div', { 'class': 'metrics-row' }, [ + E('span', { 'class': 'metrics-label' }, 'Tor Circuits'), + E('span', { 'class': 'metrics-value' }, sessionCounts.tor_circuits || 0) + ]), + E('div', { 'class': 'metrics-row' }, [ + E('span', { 'class': 'metrics-label' }, 'HTTPS Visitors'), + E('span', { 'class': 'metrics-value' }, sessionCounts.https || 0) + ]), + E('div', { 'class': 'metrics-row' }, [ + E('span', { 'class': 'metrics-label' }, 'Streamlit'), + E('span', { 'class': 'metrics-value' }, sessionCounts.streamlit || 0) + ]), + E('div', { 'class': 'metrics-row' }, [ + E('span', { 'class': 'metrics-label' }, 'Mitmproxy'), + E('span', { 'class': 'metrics-value' }, sessionCounts.mitmproxy || 0) + ]), + E('div', { 'class': 'metrics-row' }, [ + E('span', { 'class': 'metrics-label' }, 'SSH'), + E('span', { 'class': 'metrics-value' }, sessionCounts.ssh || 0) + ]) + ]), + + // Recent Visitors - NEW + E('div', { 'class': 'metrics-section sessions' }, [ + E('h3', {}, 'RECENT VISITORS'), + E('div', { 'class': 'visitor-list' }, visitorItems.length ? visitorItems : [ + E('div', { 'style': 'color:#666' }, 'No recent visitors') + ]) + ]), + + // Web Traffic E('div', { 'class': 'metrics-section traffic' }, [ E('h3', {}, 'WEB TRAFFIC'), E('div', { 'class': 'metrics-row' }, [ @@ -233,10 +314,10 @@ return view.extend({ E('div', { 'class': 'country-list' }, countryTags) ]), - // Top Hosts - NEW - E('div', { 'class': 'metrics-section traffic' }, [ - E('h3', {}, 'TOP HOSTS'), - E('div', { 'class': 'host-list' }, hostItems.length ? hostItems : [ + // Top Endpoints - NEW + E('div', { 'class': 'metrics-section sessions' }, [ + E('h3', {}, 'TOP ENDPOINTS'), + E('div', { 'class': 'endpoint-list' }, endpointItems.length ? endpointItems : [ E('div', { 'style': 'color:#666' }, 'No data') ]) ]), @@ -314,7 +395,8 @@ return view.extend({ poll.add(L.bind(function() { return Promise.all([ callGetSystemOverview(), - callGetVisitStats().catch(function() { return {}; }) + callGetVisitStats().catch(function() { return {}; }), + callGetActiveSessions().catch(function() { return {}; }) ]).then(L.bind(function(newResults) { this.updateMetrics(container, newResults); }, this)); diff --git a/package/secubox/luci-app-secubox/root/usr/share/rpcd/acl.d/luci-app-secubox.json b/package/secubox/luci-app-secubox/root/usr/share/rpcd/acl.d/luci-app-secubox.json index 6338ec66..ef741dd1 100644 --- a/package/secubox/luci-app-secubox/root/usr/share/rpcd/acl.d/luci-app-secubox.json +++ b/package/secubox/luci-app-secubox/root/usr/share/rpcd/acl.d/luci-app-secubox.json @@ -21,6 +21,8 @@ "get_system_health", "get_alerts", "get_dashboard_data", + "get_system_overview", + "get_active_sessions", "get_theme", "first_run_status", "list_apps", diff --git a/package/secubox/secubox-core/root/usr/lib/secubox/rpcd.d/dashboard.sh b/package/secubox/secubox-core/root/usr/lib/secubox/rpcd.d/dashboard.sh index c5c25b9b..f7061544 100644 --- a/package/secubox/secubox-core/root/usr/lib/secubox/rpcd.d/dashboard.sh +++ b/package/secubox/secubox-core/root/usr/lib/secubox/rpcd.d/dashboard.sh @@ -8,6 +8,7 @@ list_methods_dashboard() { add_method "get_dashboard_data" add_method "get_system_overview" + add_method "get_active_sessions" add_method "get_public_ips" add_method "refresh_public_ips" add_method_str "quick_action" "action" @@ -27,6 +28,9 @@ handle_dashboard() { get_system_overview) _do_system_overview ;; + get_active_sessions) + _do_active_sessions + ;; get_public_ips) _do_public_ips ;; @@ -61,6 +65,72 @@ _do_system_overview() { fi } +# Active Sessions - connections, visitors, endpoints +_do_active_sessions() { + json_init + + # Count active connections by service + local tor_circuits=0 streamlit_sessions=0 mitmproxy_sessions=0 https_sessions=0 ssh_sessions=0 + + tor_circuits=$(netstat -tn 2>/dev/null | grep -c ":9040.*ESTABLISHED") + streamlit_sessions=$(netstat -tn 2>/dev/null | grep -c ":8510.*ESTABLISHED") + mitmproxy_sessions=$(netstat -tn 2>/dev/null | grep -c ":8081.*ESTABLISHED") + https_sessions=$(netstat -tn 2>/dev/null | grep ":443.*ESTABLISHED" | grep -cv "127.0.0.1") + ssh_sessions=$(who 2>/dev/null | wc -l) + + json_add_object "counts" + json_add_int "tor_circuits" "${tor_circuits:-0}" + json_add_int "streamlit" "${streamlit_sessions:-0}" + json_add_int "mitmproxy" "${mitmproxy_sessions:-0}" + json_add_int "https" "${https_sessions:-0}" + json_add_int "ssh" "${ssh_sessions:-0}" + json_close_object + + # External visitor IPs on HTTPS + json_add_array "https_visitors" + netstat -tn 2>/dev/null | grep ":443.*ESTABLISHED" | grep -v "127.0.0.1" | \ + awk '{print $5}' | cut -d: -f1 | sort -u | head -10 | while read -r ip; do + [ -n "$ip" ] && json_add_string "" "$ip" + done + json_close_array + + # Top accessed endpoints (from mitmproxy log) + json_add_array "top_endpoints" + if [ -f "/srv/mitmproxy/threats.log" ]; then + tail -200 /srv/mitmproxy/threats.log 2>/dev/null | \ + jq -r '.request' 2>/dev/null | cut -d' ' -f2 | cut -d'?' -f1 | \ + sort | uniq -c | sort -rn | head -8 | \ + awk '{print "{\"path\":\"" $2 "\",\"count\":" $1 "}"}' | while read -r line; do + # Parse and add as object + local path=$(echo "$line" | jq -r '.path' 2>/dev/null) + local count=$(echo "$line" | jq -r '.count' 2>/dev/null) + json_add_object "" + json_add_string "path" "$path" + json_add_int "count" "$count" + json_close_object + done + fi + json_close_array + + # Recent unique visitors with country + json_add_array "recent_visitors" + if [ -f "/srv/mitmproxy/threats.log" ]; then + tail -100 /srv/mitmproxy/threats.log 2>/dev/null | \ + jq -r '[.source_ip, .country] | @tsv' 2>/dev/null | \ + sort -u | head -10 | while read -r ip country; do + [ -n "$ip" ] && { + json_add_object "" + json_add_string "ip" "$ip" + json_add_string "country" "${country:-??}" + json_close_object + } + done + fi + json_close_array + + json_dump +} + # Dashboard summary data (optimized - no slow appstore call) _do_dashboard_data() { json_init