secubox-openwrt/.claude/skills/double-caching.md
CyberMind-FR 1bbd345cee refactor(luci): Mass KissTheme UI rework across all LuCI apps
Convert 90+ LuCI view files from legacy cbi-button-* classes to
KissTheme kiss-btn-* classes for consistent dark theme styling.

Pattern conversions applied:
- cbi-button-positive → kiss-btn-green
- cbi-button-negative/remove → kiss-btn-red
- cbi-button-apply → kiss-btn-cyan
- cbi-button-action → kiss-btn-blue
- cbi-button (plain) → kiss-btn

Also replaced hardcoded colors (#080, #c00, #888, etc.) with
CSS variables (--kiss-green, --kiss-red, --kiss-muted, etc.)
for proper dark theme compatibility.

Apps updated include: ai-gateway, auth-guardian, bandwidth-manager,
cloner, config-advisor, crowdsec-dashboard, dns-provider, exposure,
glances, haproxy, hexojs, iot-guard, jellyfin, ksm-manager,
mac-guardian, magicmirror2, master-link, meshname-dns, metablogizer,
metabolizer, mqtt-bridge, netdata-dashboard, picobrew, routes-status,
secubox-admin, secubox-mirror, secubox-p2p, secubox-security-threats,
service-registry, simplex, streamlit, system-hub, tor-shield,
traffic-shaper, vhost-manager, vortex-dns, vortex-firewall,
webradio, wireguard-dashboard, zigbee2mqtt, zkp, and more.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-03-12 11:09:34 +01:00

4.2 KiB

Double Caching Pattern — SecuBox Skill

Overview

This pattern solves the common problem of LuCI dashboards timing out when RPC calls compute expensive aggregates from log files or database queries. The solution uses two levels of caching:

  1. Server-side static cache: Cron job writes stats to JSON file
  2. Client-side progressive loading: Show cached data immediately, enhance with async details

When to Apply

Apply this pattern when:

  • Dashboard shows aggregated stats (counts, totals, percentages)
  • RPC handler reads/parses log files
  • Data can be 1-60 seconds stale without user impact
  • Users report "XHR timeout" errors in browser console

Implementation

Step 1: Create Stats Update Script

# /usr/sbin/<module>-stats-update
#!/bin/sh
CACHE_FILE="/tmp/secubox/<module>-stats.json"
mkdir -p /tmp/secubox

# Compute expensive stats here...
count=$(wc -l < /srv/module/data.log 2>/dev/null || echo 0)
status=$(pgrep module >/dev/null && echo true || echo false)

cat > "$CACHE_FILE" << EOF
{
  "running": $status,
  "count": $count,
  "updated": "$(date -Iseconds)"
}
EOF

Step 2: Create Cron Job

# /etc/cron.d/<module>-stats
* * * * * root /usr/sbin/<module>-stats-update >/dev/null 2>&1

Step 3: Add Cached RPCD Method

# In /usr/libexec/rpcd/luci.<module>
get_cached_status() {
    local cache="/tmp/secubox/<module>-stats.json"
    if [ -f "$cache" ]; then
        cat "$cache"
    else
        echo '{"running":false,"count":0,"updated":null}'
    fi
}

# In list_methods():
"status_cached":{}

Step 4: Update LuCI JavaScript

// Use cached method for fast initial load
var callStatus = rpc.declare({
    object: 'luci.<module>',
    method: 'status_cached'  // NOT 'status'
});

// Load with timeout protection
load: function() {
    return Promise.all([
        // Fast cached status
        callStatus().catch(function() { return {}; }),

        // Slower calls with short timeout
        Promise.race([
            callDetails(),
            new Promise(function(_, reject) {
                setTimeout(function() { reject('timeout'); }, 5000);
            })
        ]).catch(function() { return { details: [] }; })
    ]);
}

Step 5: Progressive UI Enhancement

render: function(data) {
    var status = data[0] || {};  // Always available (cached)
    var details = data[1] || {}; // May be empty on timeout

    // Always render status section
    view.appendChild(renderStatus(status));

    // Conditionally render details
    if (details.items && details.items.length > 0) {
        view.appendChild(renderDetails(details));
    } else {
        view.appendChild(E('p', {}, 'Loading details...'));
        // Optionally retry async
    }
}

ACL Update

Add status_cached to ACL file:

{
  "luci-app-<module>": {
    "read": {
      "ubus": {
        "luci.<module>": ["status", "status_cached", ...]
      }
    }
  }
}

Cron Frequency Guide

Data Type Frequency Rationale
Service status 1 min Cheap, users expect ~real-time
Log counts 1 min File system operations
Network stats 1 min netstat/ss calls
Heavy aggregates 5 min Database queries, large logs
External API data 15+ min Rate limits, network latency

Files Modified

When applying this pattern, you'll typically modify:

  • /usr/sbin/<module>-stats-update (new)
  • /etc/cron.d/<module>-stats (new)
  • /usr/libexec/rpcd/luci.<module> (add cached method)
  • /www/luci-static/resources/view/<module>/status.js (use cached call)
  • /usr/share/rpcd/acl.d/luci-app-<module>.json (add to read ACL)

Troubleshooting

Cache file not updating

  • Check cron daemon: pgrep crond or /etc/init.d/cron status
  • Check script permissions: chmod +x /usr/sbin/*-stats-update
  • Check for errors: sh -x /usr/sbin/<module>-stats-update

Still getting timeouts

  • Increase RPC timeout in JS: Promise.race timeout value
  • Check if alerts/bans calls are the slow ones (move to separate tab)
  • Verify HAProxy is not routing through slow WAF for LuCI

Stale data shown

  • Verify cron job is running: cat /etc/cron.d/<module>-stats
  • Check cache file timestamp: ls -la /tmp/secubox/<module>-stats.json