feat(metrics): Add active sessions panel to SecuBox Metrics
- Add get_active_sessions RPCD method to dashboard module - Display session counts: Tor circuits, HTTPS, Streamlit, Mitmproxy, SSH - Add ACTIVE SESSIONS panel with yellow/gold theme - Add RECENT VISITORS panel showing visitor IPs and countries - Add TOP ENDPOINTS panel showing accessed paths - Add ACL permissions for get_active_sessions - Auto-refresh with other metrics every 10 seconds Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
dd6ecd2567
commit
d8578653b4
@ -15,17 +15,25 @@ var callGetVisitStats = rpc.declare({
|
|||||||
expect: { }
|
expect: { }
|
||||||
});
|
});
|
||||||
|
|
||||||
|
var callGetActiveSessions = rpc.declare({
|
||||||
|
object: 'luci.secubox',
|
||||||
|
method: 'get_active_sessions',
|
||||||
|
expect: { }
|
||||||
|
});
|
||||||
|
|
||||||
return view.extend({
|
return view.extend({
|
||||||
load: function() {
|
load: function() {
|
||||||
return Promise.all([
|
return Promise.all([
|
||||||
callGetSystemOverview(),
|
callGetSystemOverview(),
|
||||||
callGetVisitStats().catch(function() { return {}; })
|
callGetVisitStats().catch(function() { return {}; }),
|
||||||
|
callGetActiveSessions().catch(function() { return {}; })
|
||||||
]);
|
]);
|
||||||
},
|
},
|
||||||
|
|
||||||
render: function(results) {
|
render: function(results) {
|
||||||
var overview = results[0] || {};
|
var overview = results[0] || {};
|
||||||
var visitStats = results[1] || {};
|
var visitStats = results[1] || {};
|
||||||
|
var sessions = results[2] || {};
|
||||||
var sys = overview.system || {};
|
var sys = overview.system || {};
|
||||||
var net = overview.network || {};
|
var net = overview.network || {};
|
||||||
var svc = overview.services || {};
|
var svc = overview.services || {};
|
||||||
@ -33,6 +41,9 @@ return view.extend({
|
|||||||
var byCountry = visitStats.by_country || [];
|
var byCountry = visitStats.by_country || [];
|
||||||
var byHost = visitStats.by_host || [];
|
var byHost = visitStats.by_host || [];
|
||||||
var botsHumans = visitStats.bots_vs_humans || {};
|
var botsHumans = visitStats.bots_vs_humans || {};
|
||||||
|
var sessionCounts = sessions.counts || {};
|
||||||
|
var recentVisitors = sessions.recent_visitors || [];
|
||||||
|
var topEndpoints = sessions.top_endpoints || [];
|
||||||
|
|
||||||
var style = E('style', {}, `
|
var style = E('style', {}, `
|
||||||
.metrics-container {
|
.metrics-container {
|
||||||
@ -70,6 +81,10 @@ return view.extend({
|
|||||||
background: rgba(0,255,136,0.1);
|
background: rgba(0,255,136,0.1);
|
||||||
border-color: rgba(0,255,136,0.4);
|
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 {
|
.metrics-section h3 {
|
||||||
margin: 0 0 12px 0;
|
margin: 0 0 12px 0;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
@ -85,6 +100,10 @@ return view.extend({
|
|||||||
color: #00ff88;
|
color: #00ff88;
|
||||||
border-color: rgba(0,255,136,0.4);
|
border-color: rgba(0,255,136,0.4);
|
||||||
}
|
}
|
||||||
|
.metrics-section.sessions h3 {
|
||||||
|
color: #ffc800;
|
||||||
|
border-color: rgba(255,200,0,0.4);
|
||||||
|
}
|
||||||
.metrics-row {
|
.metrics-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
@ -104,6 +123,9 @@ return view.extend({
|
|||||||
.metrics-section.traffic .metrics-value {
|
.metrics-section.traffic .metrics-value {
|
||||||
color: #00ff88;
|
color: #00ff88;
|
||||||
}
|
}
|
||||||
|
.metrics-section.sessions .metrics-value {
|
||||||
|
color: #ffc800;
|
||||||
|
}
|
||||||
.metrics-bar {
|
.metrics-bar {
|
||||||
height: 8px;
|
height: 8px;
|
||||||
background: rgba(0,255,255,0.2);
|
background: rgba(0,255,255,0.2);
|
||||||
@ -132,16 +154,22 @@ return view.extend({
|
|||||||
.country-tag .flag {
|
.country-tag .flag {
|
||||||
margin-right: 4px;
|
margin-right: 4px;
|
||||||
}
|
}
|
||||||
.host-list {
|
.host-list, .visitor-list, .endpoint-list {
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
}
|
}
|
||||||
.host-item {
|
.host-item, .visitor-item, .endpoint-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
padding: 3px 0;
|
padding: 3px 0;
|
||||||
border-bottom: 1px solid rgba(0,255,136,0.1);
|
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
|
// 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' }, [
|
var container = E('div', { 'class': 'metrics-container' }, [
|
||||||
E('div', { 'class': 'metrics-header' }, 'SECUBOX SYSTEM METRICS'),
|
E('div', { 'class': 'metrics-header' }, 'SECUBOX SYSTEM METRICS'),
|
||||||
E('div', { 'class': 'metrics-grid' }, [
|
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('div', { 'class': 'metrics-section traffic' }, [
|
||||||
E('h3', {}, 'WEB TRAFFIC'),
|
E('h3', {}, 'WEB TRAFFIC'),
|
||||||
E('div', { 'class': 'metrics-row' }, [
|
E('div', { 'class': 'metrics-row' }, [
|
||||||
@ -233,10 +314,10 @@ return view.extend({
|
|||||||
E('div', { 'class': 'country-list' }, countryTags)
|
E('div', { 'class': 'country-list' }, countryTags)
|
||||||
]),
|
]),
|
||||||
|
|
||||||
// Top Hosts - NEW
|
// Top Endpoints - NEW
|
||||||
E('div', { 'class': 'metrics-section traffic' }, [
|
E('div', { 'class': 'metrics-section sessions' }, [
|
||||||
E('h3', {}, 'TOP HOSTS'),
|
E('h3', {}, 'TOP ENDPOINTS'),
|
||||||
E('div', { 'class': 'host-list' }, hostItems.length ? hostItems : [
|
E('div', { 'class': 'endpoint-list' }, endpointItems.length ? endpointItems : [
|
||||||
E('div', { 'style': 'color:#666' }, 'No data')
|
E('div', { 'style': 'color:#666' }, 'No data')
|
||||||
])
|
])
|
||||||
]),
|
]),
|
||||||
@ -314,7 +395,8 @@ return view.extend({
|
|||||||
poll.add(L.bind(function() {
|
poll.add(L.bind(function() {
|
||||||
return Promise.all([
|
return Promise.all([
|
||||||
callGetSystemOverview(),
|
callGetSystemOverview(),
|
||||||
callGetVisitStats().catch(function() { return {}; })
|
callGetVisitStats().catch(function() { return {}; }),
|
||||||
|
callGetActiveSessions().catch(function() { return {}; })
|
||||||
]).then(L.bind(function(newResults) {
|
]).then(L.bind(function(newResults) {
|
||||||
this.updateMetrics(container, newResults);
|
this.updateMetrics(container, newResults);
|
||||||
}, this));
|
}, this));
|
||||||
|
|||||||
@ -21,6 +21,8 @@
|
|||||||
"get_system_health",
|
"get_system_health",
|
||||||
"get_alerts",
|
"get_alerts",
|
||||||
"get_dashboard_data",
|
"get_dashboard_data",
|
||||||
|
"get_system_overview",
|
||||||
|
"get_active_sessions",
|
||||||
"get_theme",
|
"get_theme",
|
||||||
"first_run_status",
|
"first_run_status",
|
||||||
"list_apps",
|
"list_apps",
|
||||||
|
|||||||
@ -8,6 +8,7 @@
|
|||||||
list_methods_dashboard() {
|
list_methods_dashboard() {
|
||||||
add_method "get_dashboard_data"
|
add_method "get_dashboard_data"
|
||||||
add_method "get_system_overview"
|
add_method "get_system_overview"
|
||||||
|
add_method "get_active_sessions"
|
||||||
add_method "get_public_ips"
|
add_method "get_public_ips"
|
||||||
add_method "refresh_public_ips"
|
add_method "refresh_public_ips"
|
||||||
add_method_str "quick_action" "action"
|
add_method_str "quick_action" "action"
|
||||||
@ -27,6 +28,9 @@ handle_dashboard() {
|
|||||||
get_system_overview)
|
get_system_overview)
|
||||||
_do_system_overview
|
_do_system_overview
|
||||||
;;
|
;;
|
||||||
|
get_active_sessions)
|
||||||
|
_do_active_sessions
|
||||||
|
;;
|
||||||
get_public_ips)
|
get_public_ips)
|
||||||
_do_public_ips
|
_do_public_ips
|
||||||
;;
|
;;
|
||||||
@ -61,6 +65,72 @@ _do_system_overview() {
|
|||||||
fi
|
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)
|
# Dashboard summary data (optimized - no slow appstore call)
|
||||||
_do_dashboard_data() {
|
_do_dashboard_data() {
|
||||||
json_init
|
json_init
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user