fix(media-flow): Fix dashboard theming and flow count display
- Add SecuBox dark theme initialization to all views (dashboard, alerts,
clients, services, history)
- Fix flow count detection by using jsonfilter instead of jq (OpenWrt native)
- Prioritize /var/run/netifyd/status.json for ndpid-compat flow data
- Remove filtering expect{} from API.getActiveStreams() RPC declaration
- Update CLAUDE.md with jsonfilter usage guidelines for OpenWrt
The dashboard now correctly displays:
- Total Flows count from nDPId via ndpid-compat
- nDPId/Netifyd status indicators
- SecuBox dark theme with portal header
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
88d60cc7f4
commit
f86b67ce13
18
CLAUDE.md
18
CLAUDE.md
@ -12,6 +12,24 @@
|
||||
- `ss` command may not be available - use `netstat` or `/proc/net/tcp` as fallbacks
|
||||
- `sqlite3` may not be installed - provide fallback methods (e.g., delete database file instead of running SQL)
|
||||
|
||||
### JSON Parsing
|
||||
- **Use `jsonfilter` instead of `jq`** - jsonfilter is native to OpenWrt (part of libubox), jq is often not installed
|
||||
- Syntax examples:
|
||||
```bash
|
||||
# Get a field value
|
||||
jsonfilter -i /path/to/file.json -e '@.field_name'
|
||||
|
||||
# Get nested field
|
||||
jsonfilter -i /path/to/file.json -e '@.parent.child'
|
||||
|
||||
# Get array length (count elements)
|
||||
jsonfilter -i /path/to/file.json -e '@[*]' | wc -l
|
||||
|
||||
# Get array element
|
||||
jsonfilter -i /path/to/file.json -e '@[0]'
|
||||
```
|
||||
- Always check for empty results: `[ -z "$result" ] && result=0`
|
||||
|
||||
### Port Detection
|
||||
When checking if a port is listening, use this order of fallbacks:
|
||||
1. `/proc/net/tcp` (always available) - ports are in hex (e.g., 8080 = 1F90)
|
||||
|
||||
@ -11,7 +11,7 @@ var callStatus = rpc.declare({
|
||||
var callGetActiveStreams = rpc.declare({
|
||||
object: 'luci.media-flow',
|
||||
method: 'get_active_streams',
|
||||
expect: { streams: [] }
|
||||
expect: { }
|
||||
});
|
||||
|
||||
var callGetStreamHistory = rpc.declare({
|
||||
|
||||
@ -7,6 +7,18 @@
|
||||
'require secubox-portal/header as SbHeader';
|
||||
|
||||
return L.view.extend({
|
||||
// Initialize SecuBox dark theme
|
||||
initTheme: function() {
|
||||
document.documentElement.setAttribute('data-theme', 'dark');
|
||||
document.body.classList.add('secubox-mode');
|
||||
if (!document.getElementById('mf-theme-styles')) {
|
||||
var themeStyle = document.createElement('style');
|
||||
themeStyle.id = 'mf-theme-styles';
|
||||
themeStyle.textContent = 'body.secubox-mode { background: #0a0a0f !important; } body.secubox-mode .main-right, body.secubox-mode #maincontent, body.secubox-mode .container { background: transparent !important; }';
|
||||
document.head.appendChild(themeStyle);
|
||||
}
|
||||
},
|
||||
|
||||
load: function() {
|
||||
return Promise.all([
|
||||
API.listAlerts()
|
||||
@ -14,6 +26,9 @@ return L.view.extend({
|
||||
},
|
||||
|
||||
render: function(data) {
|
||||
var self = this;
|
||||
this.initTheme();
|
||||
|
||||
var alerts = data[0] || [];
|
||||
|
||||
var m = new form.Map('media_flow', null, null);
|
||||
|
||||
@ -6,6 +6,18 @@
|
||||
'require secubox-portal/header as SbHeader';
|
||||
|
||||
return L.view.extend({
|
||||
// Initialize SecuBox dark theme
|
||||
initTheme: function() {
|
||||
document.documentElement.setAttribute('data-theme', 'dark');
|
||||
document.body.classList.add('secubox-mode');
|
||||
if (!document.getElementById('mf-theme-styles')) {
|
||||
var themeStyle = document.createElement('style');
|
||||
themeStyle.id = 'mf-theme-styles';
|
||||
themeStyle.textContent = 'body.secubox-mode { background: #0a0a0f !important; } body.secubox-mode .main-right, body.secubox-mode #maincontent, body.secubox-mode .container { background: transparent !important; }';
|
||||
document.head.appendChild(themeStyle);
|
||||
}
|
||||
},
|
||||
|
||||
load: function() {
|
||||
return Promise.all([
|
||||
API.getStatsByClient()
|
||||
@ -13,6 +25,8 @@ return L.view.extend({
|
||||
},
|
||||
|
||||
render: function(data) {
|
||||
this.initTheme();
|
||||
|
||||
var statsByClient = data[0] || {};
|
||||
var clients = statsByClient.clients || {};
|
||||
|
||||
|
||||
@ -10,6 +10,26 @@ return view.extend({
|
||||
title: _('Media Flow Dashboard'),
|
||||
pollInterval: 5,
|
||||
|
||||
// Initialize SecuBox dark theme
|
||||
initTheme: function() {
|
||||
// Set dark theme on document
|
||||
document.documentElement.setAttribute('data-theme', 'dark');
|
||||
document.body.classList.add('secubox-mode');
|
||||
|
||||
// Apply dark background to body for SecuBox styling
|
||||
if (!document.getElementById('mf-theme-styles')) {
|
||||
var themeStyle = document.createElement('style');
|
||||
themeStyle.id = 'mf-theme-styles';
|
||||
themeStyle.textContent = `
|
||||
body.secubox-mode { background: #0a0a0f !important; }
|
||||
body.secubox-mode .main-right,
|
||||
body.secubox-mode #maincontent,
|
||||
body.secubox-mode .container { background: transparent !important; }
|
||||
`;
|
||||
document.head.appendChild(themeStyle);
|
||||
}
|
||||
},
|
||||
|
||||
formatBytes: function(bytes) {
|
||||
if (bytes === 0) return '0 B';
|
||||
var k = 1024;
|
||||
@ -28,6 +48,10 @@ return view.extend({
|
||||
|
||||
render: function(data) {
|
||||
var self = this;
|
||||
|
||||
// Initialize SecuBox dark theme
|
||||
this.initTheme();
|
||||
|
||||
var status = data[0] || {};
|
||||
var streamsData = data[1] || {};
|
||||
var statsByService = data[2] || {};
|
||||
@ -36,7 +60,7 @@ return view.extend({
|
||||
var isNdpid = dpiSource === 'ndpid';
|
||||
var isNetifyd = dpiSource === 'netifyd';
|
||||
var streams = streamsData.streams || [];
|
||||
var flowCount = streamsData.flow_count || status.active_flows || 0;
|
||||
var flowCount = streamsData.flow_count || status.active_flows || status.ndpid_flows || 0;
|
||||
|
||||
// Inject CSS
|
||||
var css = `
|
||||
|
||||
@ -6,6 +6,18 @@
|
||||
'require secubox-portal/header as SbHeader';
|
||||
|
||||
return L.view.extend({
|
||||
// Initialize SecuBox dark theme
|
||||
initTheme: function() {
|
||||
document.documentElement.setAttribute('data-theme', 'dark');
|
||||
document.body.classList.add('secubox-mode');
|
||||
if (!document.getElementById('mf-theme-styles')) {
|
||||
var themeStyle = document.createElement('style');
|
||||
themeStyle.id = 'mf-theme-styles';
|
||||
themeStyle.textContent = 'body.secubox-mode { background: #0a0a0f !important; } body.secubox-mode .main-right, body.secubox-mode #maincontent, body.secubox-mode .container { background: transparent !important; }';
|
||||
document.head.appendChild(themeStyle);
|
||||
}
|
||||
},
|
||||
|
||||
load: function() {
|
||||
return Promise.all([
|
||||
API.getStreamHistory(24)
|
||||
@ -13,6 +25,8 @@ return L.view.extend({
|
||||
},
|
||||
|
||||
render: function(data) {
|
||||
this.initTheme();
|
||||
|
||||
var historyData = data[0] || {};
|
||||
var history = historyData.history || [];
|
||||
|
||||
|
||||
@ -6,6 +6,18 @@
|
||||
'require secubox-portal/header as SbHeader';
|
||||
|
||||
return L.view.extend({
|
||||
// Initialize SecuBox dark theme
|
||||
initTheme: function() {
|
||||
document.documentElement.setAttribute('data-theme', 'dark');
|
||||
document.body.classList.add('secubox-mode');
|
||||
if (!document.getElementById('mf-theme-styles')) {
|
||||
var themeStyle = document.createElement('style');
|
||||
themeStyle.id = 'mf-theme-styles';
|
||||
themeStyle.textContent = 'body.secubox-mode { background: #0a0a0f !important; } body.secubox-mode .main-right, body.secubox-mode #maincontent, body.secubox-mode .container { background: transparent !important; }';
|
||||
document.head.appendChild(themeStyle);
|
||||
}
|
||||
},
|
||||
|
||||
load: function() {
|
||||
return Promise.all([
|
||||
API.getStatsByService()
|
||||
@ -13,6 +25,8 @@ return L.view.extend({
|
||||
},
|
||||
|
||||
render: function(data) {
|
||||
this.initTheme();
|
||||
|
||||
var statsByService = data[0] || {};
|
||||
var services = statsByService.services || {};
|
||||
|
||||
|
||||
@ -24,8 +24,15 @@ init_storage() {
|
||||
|
||||
# Detect available DPI source
|
||||
get_dpi_source() {
|
||||
# Prefer nDPId if running and has data
|
||||
if [ -f "$NDPID_FLOWS" ] && pgrep ndpid >/dev/null 2>&1; then
|
||||
# Prefer nDPId if running
|
||||
# Check for ndpid process and either direct flows file or compat layer output
|
||||
if pgrep ndpid >/dev/null 2>&1; then
|
||||
# nDPId running - check for data files
|
||||
if [ -f "$NDPID_FLOWS" ] || [ -f "$MEDIA_CACHE" ] || [ -f /var/run/netifyd/status.json ]; then
|
||||
echo "ndpid"
|
||||
return
|
||||
fi
|
||||
# nDPId running but no data yet
|
||||
echo "ndpid"
|
||||
elif [ -f /var/run/netifyd/status.json ] && pgrep netifyd >/dev/null 2>&1; then
|
||||
echo "netifyd"
|
||||
@ -124,9 +131,19 @@ case "$1" in
|
||||
ndpid_version="unknown"
|
||||
ndpid_flows=0
|
||||
pgrep ndpid > /dev/null 2>&1 && ndpid_running=1
|
||||
if [ "$ndpid_running" = "1" ] && [ -f "$NDPID_FLOWS" ]; then
|
||||
ndpid_flows=$(jq 'length' "$NDPID_FLOWS" 2>/dev/null || echo 0)
|
||||
ndpid_version=$(ndpid -v 2>/dev/null | head -1 | grep -oE '[0-9]+\.[0-9]+' | head -1 || echo "unknown")
|
||||
if [ "$ndpid_running" = "1" ]; then
|
||||
ndpid_version=$(ndpid -v 2>/dev/null | head -1 | grep -oE '[0-9]+\.[0-9]+' | head -1)
|
||||
[ -z "$ndpid_version" ] && ndpid_version="unknown"
|
||||
# Check for flows from compat layer status.json
|
||||
if [ -f /var/run/netifyd/status.json ] && [ -s /var/run/netifyd/status.json ]; then
|
||||
# Read flow_count using jsonfilter (busybox-friendly)
|
||||
ndpid_flows=$(jsonfilter -i /var/run/netifyd/status.json -e '@.flow_count' 2>/dev/null)
|
||||
[ -z "$ndpid_flows" ] && ndpid_flows=$(jsonfilter -i /var/run/netifyd/status.json -e '@.flows_active' 2>/dev/null)
|
||||
[ -z "$ndpid_flows" ] && ndpid_flows=0
|
||||
elif [ -f "$NDPID_FLOWS" ] && [ -s "$NDPID_FLOWS" ]; then
|
||||
ndpid_flows=$(jsonfilter -i "$NDPID_FLOWS" -e '@[*]' 2>/dev/null | wc -l)
|
||||
[ -z "$ndpid_flows" ] && ndpid_flows=0
|
||||
fi
|
||||
fi
|
||||
|
||||
# Check netifyd status
|
||||
@ -137,9 +154,12 @@ case "$1" in
|
||||
netifyd_flows=0
|
||||
netifyd_version="unknown"
|
||||
|
||||
if [ "$netifyd_running" = "1" ] && [ -n "$netifyd_data" ] && [ "$netifyd_data" != "{}" ]; then
|
||||
netifyd_flows=$(echo "$netifyd_data" | jq '.flows_active // .flow_count // 0' 2>/dev/null || echo 0)
|
||||
netifyd_version=$(echo "$netifyd_data" | jq -r '.agent_version // "unknown"' 2>/dev/null || echo "unknown")
|
||||
if [ "$netifyd_running" = "1" ] && [ -f /var/run/netifyd/status.json ] && [ -s /var/run/netifyd/status.json ]; then
|
||||
netifyd_flows=$(jsonfilter -i /var/run/netifyd/status.json -e '@.flows_active' 2>/dev/null)
|
||||
[ -z "$netifyd_flows" ] && netifyd_flows=$(jsonfilter -i /var/run/netifyd/status.json -e '@.flow_count' 2>/dev/null)
|
||||
[ -z "$netifyd_flows" ] && netifyd_flows=0
|
||||
netifyd_version=$(jsonfilter -i /var/run/netifyd/status.json -e '@.agent_version' 2>/dev/null)
|
||||
[ -z "$netifyd_version" ] && netifyd_version="unknown"
|
||||
fi
|
||||
|
||||
# Determine active DPI source
|
||||
@ -153,11 +173,17 @@ case "$1" in
|
||||
fi
|
||||
|
||||
history_count=0
|
||||
[ -f "$HISTORY_FILE" ] && history_count=$(jq 'length' "$HISTORY_FILE" 2>/dev/null || echo 0)
|
||||
if [ -f "$HISTORY_FILE" ]; then
|
||||
history_count=$(jq 'length' "$HISTORY_FILE" 2>/dev/null)
|
||||
[ -z "$history_count" ] && history_count=0
|
||||
fi
|
||||
|
||||
# Check if nDPId cache has active streams
|
||||
active_streams=0
|
||||
[ -f "$MEDIA_CACHE" ] && active_streams=$(jq 'length' "$MEDIA_CACHE" 2>/dev/null || echo 0)
|
||||
if [ -f "$MEDIA_CACHE" ]; then
|
||||
active_streams=$(jq 'length' "$MEDIA_CACHE" 2>/dev/null)
|
||||
[ -z "$active_streams" ] && active_streams=0
|
||||
fi
|
||||
|
||||
enabled=$(uci -q get media_flow.global.enabled 2>/dev/null || echo "1")
|
||||
refresh=$(uci -q get media_flow.global.refresh_interval 2>/dev/null || echo "5")
|
||||
@ -194,13 +220,22 @@ case "$1" in
|
||||
if [ -f "$MEDIA_CACHE" ]; then
|
||||
streams=$(cat "$MEDIA_CACHE" 2>/dev/null || echo "[]")
|
||||
fi
|
||||
if [ -f "$NDPID_FLOWS" ]; then
|
||||
flow_count=$(jq 'length' "$NDPID_FLOWS" 2>/dev/null || echo 0)
|
||||
# Get flow count - prefer compat layer status.json
|
||||
if [ -f /var/run/netifyd/status.json ] && [ -s /var/run/netifyd/status.json ]; then
|
||||
flow_count=$(jsonfilter -i /var/run/netifyd/status.json -e '@.flow_count' 2>/dev/null)
|
||||
[ -z "$flow_count" ] && flow_count=$(jsonfilter -i /var/run/netifyd/status.json -e '@.flows_active' 2>/dev/null)
|
||||
[ -z "$flow_count" ] && flow_count=0
|
||||
elif [ -f "$NDPID_FLOWS" ] && [ -s "$NDPID_FLOWS" ]; then
|
||||
flow_count=$(jsonfilter -i "$NDPID_FLOWS" -e '@[*]' 2>/dev/null | wc -l)
|
||||
[ -z "$flow_count" ] && flow_count=0
|
||||
fi
|
||||
note="Streams detected via nDPId local DPI"
|
||||
elif [ "$dpi_source" = "netifyd" ]; then
|
||||
netifyd_data=$(get_netifyd_data)
|
||||
flow_count=$(echo "$netifyd_data" | jq '.flows_active // .flow_count // 0' 2>/dev/null || echo 0)
|
||||
if [ -f /var/run/netifyd/status.json ] && [ -s /var/run/netifyd/status.json ]; then
|
||||
flow_count=$(jsonfilter -i /var/run/netifyd/status.json -e '@.flows_active' 2>/dev/null)
|
||||
[ -z "$flow_count" ] && flow_count=$(jsonfilter -i /var/run/netifyd/status.json -e '@.flow_count' 2>/dev/null)
|
||||
[ -z "$flow_count" ] && flow_count=0
|
||||
fi
|
||||
note="Application detection requires netifyd cloud subscription"
|
||||
else
|
||||
note="No DPI engine available"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user