secubox-openwrt

SecuBox & System Hub - Development Guidelines

Version: 1.0.0
Last Updated: 2025-12-28
Status: Active
Audience: Développeurs, IA assistants, mainteneurs

Ce document définit les standards, bonnes pratiques et validations obligatoires pour le développement de modules SecuBox et System Hub dans l’écosystème OpenWrt LuCI.


Table des matières

  1. Design System & UI Guidelines
  2. Architecture & Naming Conventions
  3. RPCD & ubus Best Practices
  4. ACL & Permissions
  5. JavaScript Patterns
  6. CSS/Styling Standards
  7. Common Errors & Solutions
  8. Validation Checklist
  9. Deployment Procedures
  10. AI Assistant Context Files

Design System & UI Guidelines

Color Palette (Demo-inspired)

IMPORTANT: Toujours utiliser la palette définie dans system-hub/common.css

--sh-text-primary: #fafafa;
--sh-text-secondary: #a0a0b0;
--sh-bg-primary: #0a0a0f;      /* Fond principal (noir profond) */
--sh-bg-secondary: #12121a;     /* Fond cartes/sections */
--sh-bg-tertiary: #1a1a24;      /* Fond hover/actif */
--sh-bg-card: #12121a;
--sh-border: #2a2a35;
--sh-primary: #6366f1;          /* Indigo */
--sh-primary-end: #8b5cf6;      /* Violet (pour dégradés) */
--sh-success: #22c55e;          /* Vert */
--sh-danger: #ef4444;           /* Rouge */
--sh-warning: #f59e0b;          /* Orange */

Light Mode (Secondary)

--sh-text-primary: #0f172a;
--sh-text-secondary: #475569;
--sh-bg-primary: #ffffff;
--sh-bg-secondary: #f8fafc;
--sh-bg-tertiary: #f1f5f9;
--sh-bg-card: #ffffff;
--sh-border: #e2e8f0;

✅ TOUJOURS utiliser les CSS variables - Ne JAMAIS hardcoder les couleurs.

Typography

Fonts Stack

/* Texte général */
font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;

/* Valeurs numériques, IDs, code */
font-family: 'JetBrains Mono', 'Courier New', monospace;

Import requis (ajouté dans common.css):

@import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600;700&family=Inter:wght@400;500;600;700&display=swap');

Font Sizes

/* Titres */
--sh-title-xl: 28px;    /* Page titles */
--sh-title-lg: 20px;    /* Card titles */
--sh-title-md: 16px;    /* Section headers */

/* Texte */
--sh-text-base: 14px;   /* Body text */
--sh-text-sm: 13px;     /* Labels, meta */
--sh-text-xs: 11px;     /* Uppercase labels */

/* Valeurs */
--sh-value-xl: 40px;    /* Large metrics */
--sh-value-lg: 32px;    /* Stats overview */
--sh-value-md: 28px;    /* Badges */

Component Patterns

Component Hierarchy

The following diagram shows the standard page structure and component relationships:

graph TB
    PAGE[Page Container<br/>.module-dashboard] --> HEADER[sh-page-header]
    PAGE --> CONTENT[sh-content]

    HEADER --> TITLE_SECTION[Title Section<br/>div]
    HEADER --> STATS[sh-stats-grid]

    TITLE_SECTION --> TITLE[sh-page-title<br/>gradient text]
    TITLE_SECTION --> SUBTITLE[sh-page-subtitle]

    STATS --> BADGE1[sh-stat-badge]
    STATS --> BADGE2[sh-stat-badge]
    STATS --> BADGE3[...]

    BADGE1 --> VALUE1[sh-stat-value<br/>monospace font]
    BADGE1 --> LABEL1[sh-stat-label<br/>uppercase]

    CONTENT --> TABS[sh-filter-tabs]
    CONTENT --> CARD_GRID[Card Grid<br/>grid layout]

    TABS --> TAB1[sh-filter-tab<br/>active]
    TABS --> TAB2[sh-filter-tab]

    CARD_GRID --> CARD1[sh-card]
    CARD_GRID --> CARD2[sh-card-success]
    CARD_GRID --> CARD3[sh-card-danger]

    CARD1 --> CH1[sh-card-header]
    CARD1 --> CB1[sh-card-body]

    CH1 --> CT1[sh-card-title]
    CT1 --> ICON1[sh-card-title-icon]

    CB1 --> BUTTONS[Button Group]
    CB1 --> INFO[Info Rows]

    BUTTONS --> BTN1[sh-btn<br/>sh-btn-primary]
    BUTTONS --> BTN2[sh-btn<br/>sh-btn-secondary]

    style PAGE fill:#0a0a0f,color:#fafafa,stroke:#6366f1,stroke-width:3px
    style HEADER fill:#12121a,color:#fafafa,stroke:#6366f1,stroke-width:2px
    style CONTENT fill:#12121a,color:#fafafa,stroke:#6366f1,stroke-width:2px
    style CARD1 fill:#12121a,color:#fafafa,stroke:#6366f1,stroke-width:2px
    style CARD2 fill:#12121a,color:#fafafa,stroke:#22c55e,stroke-width:2px
    style CARD3 fill:#12121a,color:#fafafa,stroke:#ef4444,stroke-width:2px
    style TITLE fill:#6366f1,color:#fff
    style BTN1 fill:#6366f1,color:#fff
    style VALUE1 fill:#8b5cf6,color:#fff

Component Categories:

  1. Layout Containers: Page wrapper, header, content sections
  2. Typography: Titles with gradient effects, subtitles, labels
  3. Data Display: Stat badges with monospace values, cards with borders
  4. Navigation: Filter tabs, nav tabs (sticky)
  5. Interactive: Buttons with gradients and hover effects

Styling Rules:


1. Page Header (Standard)

REQUIREMENT: Every module view MUST begin with this compact .sh-page-header. Do not introduce bespoke hero sections or oversized banners; the header keeps height predictable (title + subtitle on the left, stats on the right) and guarantees consistency across SecuBox dashboards. If no stats are needed, keep the container but supply an empty .sh-stats-grid for future metrics.

Slim variant: When the page only needs 2‑3 metrics, use .sh-page-header-lite + .sh-header-chip (see luci-app-vhost-manager and luci-app-secubox settings). Chips carry an emoji/icon, a tiny label, and the value; colors (.success, .danger, .warn) communicate state. This variant replaces the bulky hero blocks from older demos.

Version chip: Always expose the package version from the RPC backend (read from /usr/lib/opkg/info/<pkg>.control) and display it as the first chip (icon: 🏷️). That keeps the UI and PKG_VERSION in sync without hunting for hard-coded strings.

HTML Structure:

E('div', { 'class': 'sh-page-header' }, [
    E('div', {}, [
        E('h2', { 'class': 'sh-page-title' }, [
            E('span', { 'class': 'sh-page-title-icon' }, '🎯'),
            'Page Title'
        ]),
        E('p', { 'class': 'sh-page-subtitle' }, 'Description of the page')
    ]),
    E('div', { 'class': 'sh-stats-grid' }, [
        // Stats badges here
    ])
])

CSS Classes:

2. Stats Badges

RÈGLE: Minimum 130px, police monospace pour valeurs

E('div', { 'class': 'sh-stat-badge' }, [
    E('div', { 'class': 'sh-stat-value' }, '92'),
    E('div', { 'class': 'sh-stat-label' }, 'CPU %')
])

Grid Layout:

.sh-stats-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(130px, 1fr));
    gap: 12px;
}

3. Cards avec bordure colorée

OBLIGATOIRE: Toutes les cards doivent avoir une bordure top de 3px

E('div', { 'class': 'sh-card sh-card-success' }, [
    E('div', { 'class': 'sh-card-header' }, [
        E('h3', { 'class': 'sh-card-title' }, [
            E('span', { 'class': 'sh-card-title-icon' }, '⚙️'),
            'Card Title'
        ])
    ]),
    E('div', { 'class': 'sh-card-body' }, [
        // Content
    ])
])

Variants de bordure:

4. Buttons

Gradient buttons (preferred):

E('button', { 'class': 'sh-btn sh-btn-primary' }, 'Primary Action')
E('button', { 'class': 'sh-btn sh-btn-success' }, 'Success Action')
E('button', { 'class': 'sh-btn sh-btn-danger' }, 'Danger Action')
E('button', { 'class': 'sh-btn sh-btn-secondary' }, 'Secondary Action')

Tous les buttons doivent avoir:

5. Filter Tabs

E('div', { 'class': 'sh-filter-tabs' }, [
    E('div', {
        'class': 'sh-filter-tab active',
        'data-filter': 'all'
    }, [
        E('span', { 'class': 'sh-tab-icon' }, '📋'),
        E('span', { 'class': 'sh-tab-label' }, 'All')
    ])
])

Active tab styling:

Grid Systems

Stats Overview (Compact)

grid-template-columns: repeat(auto-fit, minmax(130px, 1fr));
gap: 16px;

Metric Cards (Medium)

grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
gap: 20px;

Info Cards (Large)

grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 20px;

Gradient Effects

Gradient Text (Titles)

background: linear-gradient(135deg, var(--sh-primary), var(--sh-primary-end));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;

Utiliser: .sh-gradient-text class ou .sh-page-title

Gradient Backgrounds (Buttons, Badges)

background: linear-gradient(135deg, var(--sh-primary), var(--sh-primary-end));

Gradient Borders (Top)

/* 3px top border avec dégradé */
.element::before {
    content: '';
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    height: 3px;
    background: linear-gradient(90deg, var(--sh-primary), var(--sh-primary-end));
}

Animation Standards

Hover Effects

transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
transform: translateY(-3px);  /* Cards */
transform: translateY(-2px);  /* Buttons, badges */

Shadow Progression

/* Default */
box-shadow: none;

/* Hover - Subtle */
box-shadow: 0 8px 20px var(--sh-shadow);

/* Hover - Pronounced */
box-shadow: 0 12px 28px var(--sh-hover-shadow);

/* Button Hover */
box-shadow: 0 8px 20px rgba(99, 102, 241, 0.5);

Architecture & Naming Conventions

System Architecture Overview

The following diagram illustrates the complete data flow from browser JavaScript to system backend:

graph TB
    subgraph "Browser"
        UI[JavaScript View<br/>view/module/overview.js]
        API[API Module<br/>module/api.js]
    end

    subgraph "LuCI Framework"
        RPC[RPC Layer<br/>L.rpc.declare]
        UHTTPD[uhttpd<br/>Web Server]
    end

    subgraph "Backend Services"
        RPCD[RPCD Daemon]
        SCRIPT[RPCD Script<br/>/usr/libexec/rpcd/luci.module-name]
        UBUS[ubus Message Bus]
    end

    subgraph "System Layer"
        UCI[UCI Configuration]
        SYS[System Services<br/>init.d scripts]
        FS[Filesystem<br/>proc, sys, etc]
    end

    UI -->|"API.getStatus()"| API
    API -->|"rpc.declare({ object: 'luci.module' })"| RPC
    RPC -->|"HTTP POST /ubus"| UHTTPD
    UHTTPD -->|"call method"| RPCD
    RPCD -->|"execute script"| SCRIPT
    SCRIPT -->|"ubus call"| UBUS
    UBUS -->|"read/write"| UCI
    UBUS -->|"control"| SYS
    SCRIPT -->|"read metrics"| FS

    style UI fill:#6366f1,color:#fff,stroke:#4f46e5
    style API fill:#8b5cf6,color:#fff,stroke:#7c3aed
    style SCRIPT fill:#22c55e,color:#fff,stroke:#16a34a
    style RPCD fill:#f59e0b,color:#fff,stroke:#d97706
    style UCI fill:#ef4444,color:#fff,stroke:#dc2626

Key Components:

  1. Browser Layer: JavaScript views and API modules handle UI and data requests
  2. LuCI Framework: RPC layer translates JavaScript calls to ubus protocol
  3. Backend Services: RPCD executes shell scripts via ubus message bus
  4. System Layer: UCI configs, system services, and filesystem provide data

Critical Naming Rule: The RPCD script name MUST match the object parameter in JavaScript’s rpc.declare().


CRITICAL: RPCD Script Naming

RÈGLE ABSOLUE: Le nom du fichier RPCD DOIT correspondre EXACTEMENT au nom de l’objet ubus dans JavaScript.

✅ CORRECT:

JavaScript:

var callStatus = rpc.declare({
    object: 'luci.system-hub',  // ← Nom objet
    method: 'getHealth'
});

Fichier RPCD:

root/usr/libexec/rpcd/luci.system-hub  # ← EXACT MATCH

❌ INCORRECT (Causes d’erreur -32000):

# Mauvais - manque le préfixe
root/usr/libexec/rpcd/system-hub

# Mauvais - underscore au lieu de tiret
root/usr/libexec/rpcd/luci.system_hub

# Mauvais - nom différent
root/usr/libexec/rpcd/systemhub

RÈGLE: Les chemins dans menu.d/*.json doivent correspondre EXACTEMENT aux fichiers de vue.

✅ CORRECT:

Menu JSON:

{
    "action": {
        "type": "view",
        "path": "system-hub/overview"
    }
}

Fichier de vue:

htdocs/luci-static/resources/view/system-hub/overview.js

❌ INCORRECT (Causes 404):

Menu: "path": "system-hub/overview" mais fichier: view/systemhub/overview.js

Prefixes Standards

Type Prefix Exemple
ubus objects luci. luci.system-hub
CSS classes sh- (System Hub) ou sb- (SecuBox) .sh-page-header
CSS variables --sh- --sh-primary
JavaScript modules Nom du module system-hub/api.js

File Structure Template

luci-app-<module-name>/
├── Makefile
├── README.md
├── htdocs/luci-static/resources/
│   ├── view/<module-name>/
│   │   ├── overview.js         # Page principale
│   │   ├── settings.js         # Configuration
│   │   └── *.js                # Autres vues
│   └── <module-name>/
│       ├── api.js              # RPC client
│       ├── theme.js            # Theme helpers (optionnel)
│       ├── common.css          # Styles partagés
│       └── *.css               # Styles spécifiques
└── root/
    ├── usr/
    │   ├── libexec/rpcd/
    │   │   └── luci.<module-name>    # ⚠️ MUST match ubus object
    │   └── share/
    │       ├── luci/menu.d/
    │       │   └── luci-app-<module-name>.json
    │       └── rpcd/acl.d/
    │           └── luci-app-<module-name>.json
    └── etc/config/<module-name> (optionnel)

RPCD & ubus Best Practices

RPCD Script Template (Shell)

Fichier: root/usr/libexec/rpcd/luci.<module-name>

#!/bin/sh
# RPCD backend for <module-name>
# ubus object: luci.<module-name>

case "$1" in
    list)
        # Liste des méthodes disponibles
        echo '{
            "getStatus": {},
            "getHealth": {},
            "getServices": {}
        }'
        ;;
    call)
        case "$2" in
            getStatus)
                # TOUJOURS retourner du JSON valide
                printf '{"enabled": true, "version": "1.0.0"}\n'
                ;;
            getHealth)
                # Lire les métriques système
                cpu_usage=$(top -bn1 | grep "CPU:" | awk '{print $2}' | sed 's/%//')
                mem_total=$(free | grep Mem | awk '{print $2}')
                mem_used=$(free | grep Mem | awk '{print $3}')

                printf '{
                    "cpu": {"usage": %s},
                    "memory": {"total_kb": %s, "used_kb": %s}
                }\n' "$cpu_usage" "$mem_total" "$mem_used"
                ;;
            getServices)
                # Exemple avec services
                services='[]'
                for service in /etc/init.d/*; do
                    # Build JSON array
                    :
                done
                echo "$services"
                ;;
            *)
                echo '{"error": "Method not found"}'
                exit 1
                ;;
        esac
        ;;
esac

RPCD Script Validation

CHECKLIST OBLIGATOIRE:

  1. ✅ Fichier exécutable: chmod +x root/usr/libexec/rpcd/luci.<module-name>
  2. ✅ Shebang présent: #!/bin/sh
  3. ✅ Structure case/esac correcte
  4. ✅ Méthode list retourne JSON avec toutes les méthodes
  5. ✅ Méthode call gère tous les cas
  6. ✅ Toujours retourner du JSON valide
  7. ✅ Pas de echo de debug (commentés en prod)
  8. ✅ Gestion d’erreur pour méthodes inconnues

Testing RPCD Scripts

Sur le routeur:

# Test direct
/usr/libexec/rpcd/luci.system-hub list

# Via ubus
ubus list luci.system-hub
ubus call luci.system-hub getStatus

# Restart RPCD après modification
/etc/init.d/rpcd restart

Common RPCD Errors

Error: “Object not found” (-32000)

Cause: Nom du fichier RPCD ne correspond pas à l’objet ubus

Solution:

# Vérifier le nom dans JS
grep -r "object:" htdocs/luci-static/resources/view/ --include="*.js"

# Renommer le fichier RPCD pour correspondre
mv root/usr/libexec/rpcd/wrong-name root/usr/libexec/rpcd/luci.correct-name

Error: “Method not found” (-32601)

Cause: Méthode non déclarée dans list ou non implémentée dans call

Solution:

# Vérifier que la méthode est dans les deux blocs
grep "getStatus" root/usr/libexec/rpcd/luci.*

Error: Invalid JSON returned

Cause: Output RPCD n’est pas du JSON valide

Solution:

# Tester le JSON
/usr/libexec/rpcd/luci.module-name call getStatus | jsonlint

# Utiliser printf au lieu de echo pour le JSON
printf '{"key": "%s"}\n' "$value"

ACL & Permissions

ACL File Template

Fichier: root/usr/share/rpcd/acl.d/luci-app-<module-name>.json

{
    "luci-app-<module-name>": {
        "description": "Grant access to <Module Name>",
        "read": {
            "ubus": {
                "luci.<module-name>": [
                    "getStatus",
                    "getHealth",
                    "getServices"
                ]
            },
            "uci": [
                "<module-name>"
            ]
        },
        "write": {
            "ubus": {
                "luci.<module-name>": [
                    "setConfig",
                    "restartService"
                ]
            },
            "uci": [
                "<module-name>"
            ]
        }
    }
}

ACL Best Practices

  1. Séparation read/write: Ne donnez que les permissions nécessaires
  2. Liste explicite: Listez toutes les méthodes ubus utilisées
  3. UCI access: Ajoutez les configs UCI dans read et write
  4. Validation JSON: Toujours valider avec jsonlint

Common ACL Errors

Error: “Access denied”

Cause: Méthode ubus pas dans ACL

Solution:

{
    "read": {
        "ubus": {
            "luci.system-hub": [
                "getHealth"  //  Ajouter la méthode manquante
            ]
        }
    }
}

Error: “UCI config not accessible”

Cause: Config UCI pas dans ACL

Solution:

{
    "read": {
        "uci": [
            "system-hub"  //  Ajouter le config
        ]
    }
}

JavaScript Patterns

API Module Template

Fichier: htdocs/luci-static/resources/<module-name>/api.js

'use strict';
'require rpc';
'require uci';

return L.Class.extend({
    // Déclarer les appels RPC
    callGetStatus: rpc.declare({
        object: 'luci.<module-name>',
        method: 'getStatus',
        expect: { }
    }),

    callGetHealth: rpc.declare({
        object: 'luci.<module-name>',
        method: 'getHealth',
        expect: { }
    }),

    // Méthodes wrapper avec gestion d'erreur
    getStatus: function() {
        return this.callGetStatus().catch(function(err) {
            console.error('Failed to get status:', err);
            return { enabled: false, error: err.message };
        });
    },

    getHealth: function() {
        return this.callGetHealth().catch(function(err) {
            console.error('Failed to get health:', err);
            return {
                cpu: { usage: 0 },
                memory: { usage: 0 },
                error: err.message
            };
        });
    },

    // Utilitaires
    formatBytes: function(bytes) {
        if (bytes === 0) return '0 B';
        var k = 1024;
        var sizes = ['B', 'KB', 'MB', 'GB'];
        var i = Math.floor(Math.log(bytes) / Math.log(k));
        return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
    }
});

View Template

Fichier: htdocs/luci-static/resources/view/<module-name>/overview.js

'use strict';
'require view';
'require ui';
'require dom';
'require poll';
'require <module-name>/api as API';

return view.extend({
    // State
    healthData: null,
    sysInfo: null,

    // Load data
    load: function() {
        return Promise.all([
            API.getStatus(),
            API.getHealth()
        ]);
    },

    // Render UI
    render: function(data) {
        var self = this;
        this.sysInfo = data[0] || {};
        this.healthData = data[1] || {};

        var container = E('div', { 'class': '<module>-dashboard' }, [
            // Link CSS files
            E('link', { 'rel': 'stylesheet', 'href': L.resource('<module>/common.css') }),
            E('link', { 'rel': 'stylesheet', 'href': L.resource('<module>/overview.css') }),

            // Header
            this.renderHeader(),

            // Content
            this.renderContent()
        ]);

        // Setup auto-refresh
        poll.add(L.bind(function() {
            return Promise.all([
                API.getStatus(),
                API.getHealth()
            ]).then(L.bind(function(refreshData) {
                this.sysInfo = refreshData[0] || {};
                this.healthData = refreshData[1] || {};
                this.updateDashboard();
            }, this));
        }, this), 30); // Refresh every 30s

        return container;
    },

    renderHeader: function() {
        return E('div', { 'class': 'sh-page-header' }, [
            // Header content
        ]);
    },

    renderContent: function() {
        return E('div', { 'class': 'sh-content' }, [
            // Main content
        ]);
    },

    updateDashboard: function() {
        // Update existing DOM elements
        var element = document.querySelector('.my-element');
        if (element) {
            dom.content(element, this.renderContent());
        }
    },

    // Required stubs for LuCI
    handleSaveApply: null,
    handleSave: null,
    handleReset: null
});

Event Handling Pattern

// ✅ CORRECT: Bind events après render
render: function(data) {
    var container = E('div', {}, [
        E('button', {
            'id': 'my-button',
            'class': 'sh-btn sh-btn-primary'
        }, 'Click Me')
    ]);

    // Ajouter l'événement après le container est créé
    container.addEventListener('click', function(ev) {
        if (ev.target && ev.target.id === 'my-button') {
            self.handleButtonClick();
        }
    });

    return container;
},

handleButtonClick: function() {
    ui.addNotification(null, E('p', 'Button clicked!'), 'info');
}

Common JavaScript Errors

Error: “[object HTMLButtonElement]” affiché

Cause: Array imbriqué quand E() attend un array simple

// ❌ INCORRECT
E('div', {}, [
    this.renderButtons()  // renderButtons retourne déjà un array
])

// ✅ CORRECT
E('div', {},
    this.renderButtons()  // Pas de [ ] supplémentaire
)

Error: “Cannot read property of undefined”

Cause: Données API non disponibles

// ❌ INCORRECT
var cpuUsage = this.healthData.cpu.usage;

// ✅ CORRECT (avec optional chaining)
var cpuUsage = (this.healthData.cpu && this.healthData.cpu.usage) || 0;
// ou
var cpuUsage = this.healthData.cpu?.usage || 0; // ES2020

Error: “poll callback failed”

Cause: Promise non retournée dans poll.add

// ❌ INCORRECT
poll.add(function() {
    API.getHealth(); // Pas de return!
}, 30);

// ✅ CORRECT
poll.add(function() {
    return API.getHealth().then(function(data) {
        // Update UI
    });
}, 30);

CSS/Styling Standards

File Organization

<module-name>/
├── common.css       # Shared components (headers, buttons, cards, tabs)
├── overview.css     # Overview page specific
├── services.css     # Services page specific
└── *.css            # Other page-specific styles

CSS File Template

/**
 * Module Name - Page/Component Styles
 * Description of what this file styles
 * Version: X.Y.Z
 */

/* === Import shared styles (if needed) === */
/* Not required if loaded in HTML */

/* === Page-specific variables (if needed) === */
:root {
    --page-specific-var: value;
}

/* === Layout === */
.module-page-container {
    /* Layout styles */
}

/* === Components === */
.module-component {
    /* Component styles */
}

/* === Responsive === */
@media (max-width: 768px) {
    /* Mobile styles */
}

/* === Dark Mode Overrides === */
[data-theme="dark"] .module-component {
    /* Dark mode specific */
}

CSS Best Practices

1. TOUJOURS utiliser les variables CSS

/* ❌ INCORRECT */
.my-card {
    background: #12121a;
    color: #fafafa;
}

/* ✅ CORRECT */
.my-card {
    background: var(--sh-bg-card);
    color: var(--sh-text-primary);
}

2. Prefix classes par module

/* System Hub */
.sh-page-header { }
.sh-card { }
.sh-btn { }

/* SecuBox */
.sb-module-grid { }
.sb-dashboard { }

/* Module spécifique */
.netdata-chart { }
.crowdsec-alert { }

3. Transitions cohérentes

/* Standard transition */
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);

/* Quick transition (hover states) */
transition: all 0.2s ease;

/* Smooth transition (large movements) */
transition: all 0.5s ease;

4. Responsive breakpoints

/* Mobile */
@media (max-width: 768px) {
    .sh-stats-grid {
        grid-template-columns: repeat(2, 1fr);
    }
}

/* Tablet */
@media (min-width: 769px) and (max-width: 1024px) {
    /* Tablet specific */
}

/* Desktop */
@media (min-width: 1025px) {
    /* Desktop specific */
}

5. Dark mode OBLIGATOIRE

Toujours fournir des styles dark mode:

/* Light mode (default) */
.my-component {
    background: var(--sh-bg-card);
    border: 1px solid var(--sh-border);
}

/* Dark mode override */
[data-theme="dark"] .my-component {
    background: var(--sh-bg-card);
    border-color: var(--sh-border);
}

Z-index Scale

Respecter cette échelle:

--z-base: 0;
--z-dropdown: 100;
--z-sticky: 200;
--z-fixed: 300;
--z-modal-backdrop: 400;
--z-modal: 500;
--z-popover: 600;
--z-tooltip: 700;

Common Errors & Solutions

1. RPCD Object Not Found (-32000)

Erreur complète:

RPC call to luci.system-hub/getHealth failed with error -32000: Object not found

Diagnostic:

# 1. Vérifier que le fichier RPCD existe
ls -la /usr/libexec/rpcd/luci.system-hub

# 2. Vérifier qu'il est exécutable
chmod +x /usr/libexec/rpcd/luci.system-hub

# 3. Lister les objets ubus
ubus list | grep system-hub

# 4. Si absent, redémarrer RPCD
/etc/init.d/rpcd restart
ubus list | grep system-hub

Solutions:

  1. Renommer le fichier RPCD pour correspondre exactement
  2. Vérifier permissions (755 ou rwxr-xr-x)
  3. Redémarrer rpcd

2. View Not Found (404)

Erreur:

HTTP error 404 while loading class file '/luci-static/resources/view/system-hub/overview.js'

Diagnostic:

# 1. Vérifier que le fichier existe
ls -la /www/luci-static/resources/view/system-hub/overview.js

# 2. Vérifier le chemin dans menu.d
grep "path" /usr/share/luci/menu.d/luci-app-system-hub.json

Solutions:

  1. Vérifier que le path dans menu JSON correspond au fichier
  2. Vérifier permissions du fichier (644)
  3. Nettoyer cache: rm -f /tmp/luci-indexcache /tmp/luci-modulecache/*

3. CSS Not Loading (403 Forbidden)

Erreur:

GET /luci-static/resources/system-hub/common.css 403 Forbidden

Diagnostic:

# Vérifier permissions
ls -la /www/luci-static/resources/system-hub/common.css

Solution:

# Corriger permissions
chmod 644 /www/luci-static/resources/system-hub/*.css

4. Invalid JSON from RPCD

Erreur dans browser console:

SyntaxError: Unexpected token in JSON at position X

Diagnostic:

# Tester le JSON directement
/usr/libexec/rpcd/luci.system-hub call getHealth | jsonlint

# Ou avec jq
/usr/libexec/rpcd/luci.system-hub call getHealth | jq .

Solutions courantes:

# ❌ INCORRECT - Quote simple non échappée
echo '{"error": "can't process"}'

# ✅ CORRECT - Utiliser printf et doubles quotes
printf '{"error": "cannot process"}\n'

# ❌ INCORRECT - Variable non quotée
echo "{\"value\": $var}"

# ✅ CORRECT - Variable quotée
printf '{"value": "%s"}\n' "$var"

5. Browser Cache Issues

Symptômes:

Solutions:

# 1. Côté serveur - nettoyer cache LuCI
ssh root@router "rm -f /tmp/luci-indexcache /tmp/luci-modulecache/* && /etc/init.d/uhttpd restart"

# 2. Côté client - hard refresh
Ctrl + Shift + R (Chrome/Firefox)
Ctrl + F5 (Windows)
Cmd + Shift + R (Mac)

# 3. Mode privé/incognito pour test
Ctrl + Shift + N (Chrome)
Ctrl + Shift + P (Firefox)

6. ACL Access Denied

Erreur:

Access to path '/admin/secubox/system/system-hub' denied

Diagnostic:

# Vérifier ACL
cat /usr/share/rpcd/acl.d/luci-app-system-hub.json | jq .

# Vérifier que méthodes ubus sont listées
grep "getHealth" /usr/share/rpcd/acl.d/luci-app-system-hub.json

Solution: Ajouter la méthode manquante dans ACL et redémarrer rpcd.


Validation Checklist

Pre-Commit Checklist

Avant chaque commit, vérifier:

Pre-Deploy Checklist

Avant déploiement sur routeur:

Post-Deploy Checklist

Après déploiement:


Deployment Procedures

Deployment Workflow

The following flowchart illustrates the complete deployment process with validation checkpoints:

flowchart TD
    START([Start Deployment]) --> LOCAL_VAL{Local Validation<br/>Passed?}
    LOCAL_VAL -->|No| FIX_LOCAL[Fix Issues Locally]
    FIX_LOCAL --> LOCAL_VAL
    LOCAL_VAL -->|Yes| CHECK_DISK{Disk Space<br/>< 90%?}

    CHECK_DISK -->|No| CLEAN_DISK[Clean Temp Files<br/>& Old Backups]
    CLEAN_DISK --> CHECK_DISK
    CHECK_DISK -->|Yes| FIX_PERM_LOCAL[Fix Permissions<br/>Local Source]

    FIX_PERM_LOCAL --> COPY[Copy Files to Router<br/>scp JS/CSS/RPCD]
    COPY --> FIX_PERM_REMOTE[Fix Permissions<br/>Remote Files<br/>755 RPCD / 644 CSS-JS]
    FIX_PERM_REMOTE --> CLEAR[Clear LuCI Cache<br/>/tmp/luci-*]
    CLEAR --> RESTART[Restart Services<br/>rpcd + uhttpd]

    RESTART --> V1{ubus Object<br/>Available?}
    V1 -->|No| DEBUG1[Debug RPCD Script<br/>Check naming & permissions]
    DEBUG1 --> FIX_PERM_REMOTE

    V1 -->|Yes| V2{Files<br/>Accessible?}
    V2 -->|403 Error| DEBUG2[Fix File Permissions<br/>chmod 644]
    DEBUG2 --> FIX_PERM_REMOTE

    V2 -->|Yes| V3{Menu Path<br/>Matches View?}
    V3 -->|404 Error| DEBUG3[Fix Menu JSON Path]
    DEBUG3 --> COPY

    V3 -->|Yes| V4{UI Loads<br/>Correctly?}
    V4 -->|Errors| DEBUG4[Check Browser Console<br/>Fix JavaScript Errors]
    DEBUG4 --> COPY

    V4 -->|Yes| TEST[Browser Testing<br/>Private Mode<br/>Dark/Light Mode<br/>Responsive]
    TEST --> SUCCESS([✅ Deployment Success])

    style START fill:#6366f1,color:#fff,stroke:#4f46e5
    style SUCCESS fill:#22c55e,color:#fff,stroke:#16a34a
    style DEBUG1 fill:#ef4444,color:#fff,stroke:#dc2626
    style DEBUG2 fill:#ef4444,color:#fff,stroke:#dc2626
    style DEBUG3 fill:#ef4444,color:#fff,stroke:#dc2626
    style DEBUG4 fill:#ef4444,color:#fff,stroke:#dc2626
    style CHECK_DISK fill:#f59e0b,color:#fff,stroke:#d97706
    style LOCAL_VAL fill:#8b5cf6,color:#fff,stroke:#7c3aed

Deployment Stages:

  1. Local Validation: Run validate-modules.sh and fix-permissions.sh --local
  2. Pre-Flight Checks: Disk space and permission verification
  3. File Transfer: Copy JavaScript, CSS, and RPCD scripts
  4. Remote Setup: Fix permissions and clear caches
  5. Service Restart: Reload rpcd and uhttpd daemons
  6. Validation: Multi-stage verification (ubus, files, menu, UI)
  7. Testing: Browser testing in private mode

Common Error Recovery Paths:


⚠️ Pre-Deployment Checks (CRITICAL)

TOUJOURS exécuter ces vérifications AVANT tout déploiement:

1. Vérification de l’Espace Disque

# Sur le routeur cible
ssh root@192.168.8.191 "df -h | grep overlay"

# Vérifier que l'utilisation est < 90%
# Exemple OK:
# /dev/loop0    98.8M    45.2M    53.6M   46% /overlay

# Exemple CRITIQUE (STOP deployment):
# /dev/loop0    98.8M    98.8M       0  100% /overlay  ← PLEIN!

Si l’overlay est plein (≥95%):

# Libérer de l'espace avant déploiement
ssh root@192.168.8.191 << 'EOF'
# Supprimer fichiers temporaires
rm -rf /tmp/*.ipk /tmp/luci-* 2>/dev/null

# Supprimer anciens backups (>7 jours)
find /root -name '*.backup-*' -type f -mtime +7 -delete 2>/dev/null

# Vérifier packages inutilisés
opkg list-installed | grep -E 'netdata|unused'

# Après nettoyage, vérifier l'espace libéré
df -h | grep overlay
EOF

Tailles typiques à surveiller:

2. Vérification des Permissions (Critique pour Éviter Erreurs 403)

Permissions OBLIGATOIRES:

Type Permission Octal Raison
RPCD scripts rwxr-xr-x 755 Exécutable par system
CSS files rw-r--r-- 644 Lecture web server
JS files rw-r--r-- 644 Lecture web server
JSON files rw-r--r-- 644 Lecture rpcd

Erreur commune: Fichiers créés avec 600 (rw——-) au lieu de 644

Symptôme: HTTP 403 Forbidden lors du chargement de fichiers JS/CSS

Exemple d’erreur:

NetworkError: HTTP error 403 while loading class file
"/luci-static/resources/view/netdata-dashboard/dashboard.js"

Diagnostic rapide:

# Vérifier permissions des fichiers déployés
ssh root@192.168.8.191 "ls -la /www/luci-static/resources/view/MODULE_NAME/"

# Chercher fichiers avec permissions incorrectes (600)
ssh root@192.168.8.191 "find /www/luci-static/resources/view/ -type f -name '*.js' -perm 600"

# MAUVAIS (cause 403):
# -rw-------  1 root root  9763 dashboard.js  ← 600 = pas lisible par web!

# BON:
# -rw-r--r--  1 root root  9763 dashboard.js  ← 644 = OK

Correction immédiate:

# Corriger TOUS les fichiers CSS/JS
ssh root@192.168.8.191 << 'EOF'
find /www/luci-static/resources/ -name '*.css' -exec chmod 644 {} \;
find /www/luci-static/resources/ -name '*.js' -exec chmod 644 {} \;
find /usr/libexec/rpcd/ -name 'luci.*' -exec chmod 755 {} \;
EOF

⚡ Correction Automatique (Recommandé):

Utiliser le script automatique qui vérifie et corrige toutes les permissions:

# Corriger permissions locales (source code)
./secubox-tools/fix-permissions.sh --local

# Corriger permissions sur routeur
./secubox-tools/fix-permissions.sh --remote

# Corriger les deux (local + remote)
./secubox-tools/fix-permissions.sh

Le script fix-permissions.sh effectue automatiquement:

🔍 Validation Automatique des Permissions:

Le script validate-modules.sh inclut maintenant un Check 7 qui vérifie automatiquement les permissions:

./secubox-tools/validate-modules.sh

# Check 7 validera:
# ✓ Tous les RPCD sont 755
# ✓ Tous les CSS sont 644
# ✓ Tous les JS sont 644
# ❌ Affichera erreurs si permissions incorrectes

Workflow recommandé:

  1. Développer/modifier code
  2. ./secubox-tools/fix-permissions.sh --local (avant commit)
  3. ./secubox-tools/validate-modules.sh (vérifier tout)
  4. Commit & push
  5. Deploy sur routeur
  6. ./secubox-tools/fix-permissions.sh --remote (après deploy)

3. Post-Deployment Verification

Checklist après déploiement:

#!/bin/bash
ROUTER="root@192.168.8.191"
MODULE="module-name"

echo "🔍 Post-Deployment Verification"
echo ""

# 1. Vérifier espace disque
echo "1. Espace disque restant:"
ssh "$ROUTER" "df -h | grep overlay | awk '{print \$5}'" || echo "❌ FAIL"

# 2. Vérifier permissions CSS/JS
echo "2. Permissions CSS/JS:"
ssh "$ROUTER" "find /www/luci-static/resources/$MODULE -type f \( -name '*.css' -o -name '*.js' \) ! -perm 644" | \
    if [ -z "$(cat)" ]; then echo "✅ OK"; else echo "❌ FAIL - Permissions incorrectes"; fi

# 3. Vérifier permissions RPCD
echo "3. Permissions RPCD:"
ssh "$ROUTER" "ls -l /usr/libexec/rpcd/luci.$MODULE | grep -q rwxr-xr-x" && echo "✅ OK" || echo "❌ FAIL"

# 4. Vérifier ubus object
echo "4. ubus object disponible:"
ssh "$ROUTER" "ubus list | grep -q luci.$MODULE" && echo "✅ OK" || echo "❌ FAIL"

# 5. Vérifier fichiers accessibles (HTTP)
echo "5. Fichiers web accessibles:"
ssh "$ROUTER" "test -r /www/luci-static/resources/$MODULE/common.css" && echo "✅ OK" || echo "⚠️  common.css non trouvé"

# 6. Vérifier cache cleared
echo "6. Cache LuCI cleared:"
ssh "$ROUTER" "test ! -f /tmp/luci-indexcache" && echo "✅ OK" || echo "⚠️  Cache encore présent"

echo ""
echo "✅ Vérification terminée"

4. Common Deployment Errors

Erreur Cause Solution Rapide
HTTP 403 Forbidden Permissions 600 au lieu de 644 chmod 644 *.js *.css
No space left on device Overlay plein Nettoyer /tmp, supprimer anciens backups
Object not found -32000 RPCD pas exécutable ou mal nommé chmod 755 rpcd/luci.* + vérifier nom
Module not appearing Cache LuCI pas cleared rm /tmp/luci-* + restart services
Changes not visible Cache navigateur Mode privé + Ctrl+Shift+R

5. Emergency Disk Space Recovery

Si le déploiement échoue avec “No space left on device”:

#!/bin/bash
ROUTER="root@192.168.8.191"

echo "🚨 Emergency Disk Space Recovery"
echo ""

# 1. Analyser l'utilisation
echo "Top 10 consumers:"
ssh "$ROUTER" "du -k /overlay/upper 2>/dev/null | sort -rn | head -10"

# 2. Nettoyer temporaires
echo ""
echo "Cleaning temp files..."
ssh "$ROUTER" "rm -rf /tmp/*.ipk /tmp/luci-* /root/*.ipk 2>/dev/null"

# 3. Supprimer anciens backups
echo "Removing old backups (>7 days)..."
ssh "$ROUTER" "find /root -name '*.backup-*' -mtime +7 -delete 2>/dev/null"

# 4. Option: Supprimer Netdata Web UI (libère ~22MB)
echo ""
echo "⚠️  Option: Remove Netdata Web UI (saves ~22MB)?"
read -p "Continue? (y/N) " -n 1 -r
if [[ $REPLY =~ ^[Yy]$ ]]; then
    ssh "$ROUTER" "opkg remove netdata-web 2>/dev/null || rm -rf /usr/share/netdata/web/*"
fi

# 5. Vérifier espace libéré
echo ""
echo "Space after cleanup:"
ssh "$ROUTER" "df -h | grep overlay"

Standard Deployment Script Template

#!/bin/bash
# Deploy <Module Name>

ROUTER="root@192.168.8.191"
MODULE="<module-name>"
LOCAL_DIR="/path/to/luci-app-$MODULE/htdocs/luci-static/resources"
REMOTE_DIR="/www/luci-static/resources"

echo "📦 Déploiement $MODULE"
echo ""

# 1. Deploy JS files
echo "1. Copie des fichiers JS..."
scp "$LOCAL_DIR/view/$MODULE/"*.js "$ROUTER:$REMOTE_DIR/view/$MODULE/"
scp "$LOCAL_DIR/$MODULE/api.js" "$ROUTER:$REMOTE_DIR/$MODULE/"

# 2. Deploy CSS files
echo "2. Copie des fichiers CSS..."
scp "$LOCAL_DIR/$MODULE/"*.css "$ROUTER:$REMOTE_DIR/$MODULE/"

# 3. Deploy RPCD backend
echo "3. Copie du backend RPCD..."
scp "root/usr/libexec/rpcd/luci.$MODULE" "$ROUTER:/usr/libexec/rpcd/"

# 4. Fix permissions
echo "4. Correction des permissions..."
ssh "$ROUTER" "chmod 755 /usr/libexec/rpcd/luci.$MODULE"
ssh "$ROUTER" "chmod 644 $REMOTE_DIR/$MODULE/*.css"
ssh "$ROUTER" "chmod 644 $REMOTE_DIR/view/$MODULE/*.js"

# 5. Clear cache
echo "5. Nettoyage du cache..."
ssh "$ROUTER" "rm -f /tmp/luci-indexcache /tmp/luci-modulecache/* 2>/dev/null"

# 6. Restart services
echo "6. Redémarrage des services..."
ssh "$ROUTER" "/etc/init.d/rpcd restart"
ssh "$ROUTER" "/etc/init.d/uhttpd restart"

# 7. Verify
echo ""
echo "7. Vérification..."
ssh "$ROUTER" "ubus list | grep luci.$MODULE"

echo ""
echo "✅ Déploiement terminé!"
echo ""
echo "🌐 Testez (mode privé):"
echo "   https://192.168.8.191/cgi-bin/luci/admin/secubox/path/to/$MODULE"

Rollback Procedure

En cas de problème:

#!/bin/bash
# Rollback to previous version

ROUTER="root@192.168.8.191"
BACKUP_DIR="/root/luci-backups/$(date +%Y%m%d)"

# 1. Créer backup avant deploy
ssh "$ROUTER" "mkdir -p $BACKUP_DIR"
ssh "$ROUTER" "cp -r /www/luci-static/resources/module-name $BACKUP_DIR/"
ssh "$ROUTER" "cp /usr/libexec/rpcd/luci.module-name $BACKUP_DIR/"

# 2. En cas de problème, restore
ssh "$ROUTER" "cp -r $BACKUP_DIR/module-name /www/luci-static/resources/"
ssh "$ROUTER" "cp $BACKUP_DIR/luci.module-name /usr/libexec/rpcd/"
ssh "$ROUTER" "/etc/init.d/rpcd restart && /etc/init.d/uhttpd restart"

Version Control

Toujours incrémenter les versions:

# Makefile
PKG_VERSION:=0.3.0
PKG_RELEASE:=1
/* CSS files */
/**
 * Module - Styles
 * Version: 0.3.0
 */
// JavaScript
// Version: 0.3.0

Semantic Versioning:


Quick Reference

Essential Commands

# Validation
./secubox-tools/validate-modules.sh

# Build (local)
./secubox-tools/local-build.sh build luci-app-module-name

# Deploy files
scp file.js root@router:/www/luci-static/resources/

# Fix permissions
ssh root@router "chmod 644 /www/luci-static/resources/**/*.css"
ssh root@router "chmod 755 /usr/libexec/rpcd/luci.*"

# Clear cache
ssh root@router "rm -f /tmp/luci-indexcache /tmp/luci-modulecache/*"

# Restart services
ssh root@router "/etc/init.d/rpcd restart && /etc/init.d/uhttpd restart"

# Test ubus
ssh root@router "ubus list | grep luci"
ssh root@router "ubus call luci.module-name getStatus"

# Validate JSON
jsonlint file.json
jq . file.json

CSS Classes Quick Reference

/* Layout */
.sh-page-header          /* Page header container */
.sh-page-title           /* Page title (gradient text) */
.sh-page-subtitle        /* Page subtitle */

/* Stats */
.sh-stats-grid           /* Grid for stat badges (130px min) */
.sh-stat-badge           /* Stat badge container */
.sh-stat-value           /* Stat value (monospace) */
.sh-stat-label           /* Stat label (uppercase) */

/* Cards */
.sh-card                 /* Card container (with gradient border on hover) */
.sh-card-success         /* Card with green border */
.sh-card-danger          /* Card with red border */
.sh-card-warning         /* Card with orange border */
.sh-card-header          /* Card header */
.sh-card-title           /* Card title */
.sh-card-body            /* Card content */

/* Buttons */
.sh-btn                  /* Base button */
.sh-btn-primary          /* Primary button (gradient) */
.sh-btn-success          /* Success button (green) */
.sh-btn-danger           /* Danger button (red) */
.sh-btn-secondary        /* Secondary button (outline) */

/* Tabs */
.sh-filter-tabs          /* Filter tabs container */
.sh-filter-tab           /* Filter tab */
.sh-filter-tab.active    /* Active filter tab (gradient) */
.sh-nav-tabs             /* Navigation tabs (sticky) */
.sh-nav-tab              /* Navigation tab */
.sh-nav-tab.active       /* Active nav tab (underline) */

/* Utilities */
.sh-gradient-text        /* Gradient text effect */
.sh-id-display           /* Monospace ID display */
.sh-empty-state          /* Empty state placeholder */

Color Variables Quick Reference

/* Text */
var(--sh-text-primary)      /* Main text */
var(--sh-text-secondary)    /* Secondary text */

/* Backgrounds */
var(--sh-bg-primary)        /* Main background */
var(--sh-bg-secondary)      /* Secondary background */
var(--sh-bg-tertiary)       /* Tertiary background */
var(--sh-bg-card)           /* Card background */

/* Borders */
var(--sh-border)            /* Border color */

/* Colors */
var(--sh-primary)           /* Indigo #6366f1 */
var(--sh-primary-end)       /* Violet #8b5cf6 */
var(--sh-success)           /* Green #22c55e */
var(--sh-danger)            /* Red #ef4444 */
var(--sh-warning)           /* Orange #f59e0b */

/* Effects */
var(--sh-shadow)            /* Box shadow */
var(--sh-hover-shadow)      /* Hover shadow */
var(--sh-hover-bg)          /* Hover background */

AI Assistant Context Files

SecuBox work is shared between Claude and Codex assistants. Keep the context folders synchronized so any agent can resume work quickly:

Directory File Usage
.claude/ HISTORY.md Chronological log of UI/theme changes and major deployments
.claude/ TODO.md High-level backlog (UX polish, docs, automation ideas)
.claude/ WIP.md Active tasks, risks, and immediate next steps
.codex/ HISTORY.md Mirrors the development timeline for Codex sessions
.codex/ TODO.md Tooling-focused tasks (linting, scripts, build automation)
.codex/ WIP.md Status tracker for ongoing Codex efforts

Maintenance rules

  1. Update after each session: When finishing work, append a short bullet to HISTORY and adjust WIP/TODO so they reflect the new state.
  2. Reference deployment scripts: Note which secubox-tools/*.sh script was used (dashboard vs. full deploy) so the next assistant knows how to reproduce it.
  3. Keep entries concise: A single paragraph or bullet per update is enough; detailed specs remain in DOCS.
  4. Cross-check before big changes: Read both folders before starting work to avoid conflicts or duplicate efforts.

Treat these files as living handoff notes—if they drift, onboarding a new AI/teammate becomes significantly slower.


Conclusion

Ce guide doit être consulté AVANT de:

  1. Créer un nouveau module
  2. Modifier des styles existants
  3. Ajouter des méthodes RPCD
  4. Déployer sur un routeur
  5. Débugger des erreurs

En cas de doute, TOUJOURS:

  1. Consulter ce guide
  2. Exécuter validate-modules.sh
  3. Tester en mode privé
  4. Vérifier la console browser (F12)

Ressources supplémentaires:


Dernière mise à jour: 2025-12-26 Maintenu par: CyberMind Studio Version du guide: 1.0.0