diff --git a/.claude/README.md b/.claude/README.md index bcaaf20..cf292d4 100644 --- a/.claude/README.md +++ b/.claude/README.md @@ -1,435 +1,438 @@ -# 📚 Documentation Claude pour SecuBox +# 📚 Configuration Claude Code pour SecuBox -Ce rĂ©pertoire contient la documentation et les guides pour travailler avec Claude Code sur le projet SecuBox. +**Version:** 2.0.0 +**Date:** 2025-12-26 +**Projet:** SecuBox OpenWrt +**Design System:** v0.3.0 (Demo-inspired) + +Ce rĂ©pertoire contient la configuration et les guides pour travailler avec Claude Code sur le projet SecuBox. --- -## 📁 Fichiers Disponibles +## 🚹 IMPORTANT: Nouvelle Documentation (v2.0) -### 1. `module-prompts.md` -**Prompts d'implĂ©mentation pour les 14 modules de base** +**La documentation a Ă©tĂ© entiĂšrement restructurĂ©e et amĂ©liorĂ©e!** -Contient les spĂ©cifications complĂštes pour chaque module SecuBox existant: -- SecuBox Hub, System Hub -- CrowdSec, Netdata, Netifyd Dashboards -- Network Modes, Client Guardian, Auth Guardian -- WireGuard, Bandwidth Manager, Media Flow -- VHost Manager, CDN Cache, Traffic Shaper +### 📖 Guides Principaux (À la racine du projet) -**Usage**: Copie-colle le prompt du module que tu veux implĂ©menter ou modifier. +| Guide | Description | Quand l'utiliser | +|-------|-------------|------------------| +| **[DEVELOPMENT-GUIDELINES.md](../DEVELOPMENT-GUIDELINES.md)** | ⭐ **GUIDE COMPLET** (33KB, 900+ lignes)
Design System, RPCD/ubus, ACL, JS, CSS, Debug, Validation, Deployment | AVANT toute modification de code | +| **[QUICK-START.md](../QUICK-START.md)** | ⚡ **AIDE-MÉMOIRE** (6.4KB)
RĂšgles critiques, commandes rapides, templates de code | RĂ©fĂ©rence quotidienne | +| **[CLAUDE.md](../CLAUDE.md)** | đŸ—ïž **ARCHITECTURE** (17KB)
Build OpenWrt, structure fichiers, CI/CD | Build et architecture | +| **[README.md](../README.md)** | 📘 **VUE D'ENSEMBLE** (18KB)
PrĂ©sentation projet, modules, quick start | Introduction au projet | -### 2. `module-implementation-guide.md` ⭐ NOUVEAU -**Template structurĂ© pour crĂ©er de nouveaux modules** +### 🔧 Scripts & Tools -Guide complet avec: -- Template rĂ©utilisable pour tout nouveau module -- Checklist de validation complĂšte -- Exemple dĂ©taillĂ©: KSM Manager (gestion de clĂ©s + Nitrokey) -- Workflow d'implĂ©mentation Ă©tape par Ă©tape -- SpĂ©cifications techniques dĂ©taillĂ©es - -**Usage**: -1. Copie le template -2. Remplis les sections pour ton module -3. Soumets le prompt complet Ă  Claude -4. Valide avec `validate-modules.sh` - -### 3. `settings.local.json` -**Configuration locale de Claude Code** - -Contient les paramĂštres de dĂ©veloppement pour cette session. +| Script | Description | Usage | +|--------|-------------|-------| +| **[deploy-module-template.sh](../deploy-module-template.sh)** | Script de dĂ©ploiement standardisĂ© avec backup | `./deploy-module-template.sh ` | +| **validate-modules.sh** | Validation complĂšte des modules | `./secubox-tools/validate-modules.sh` | +| **local-build.sh** | Build local avec SDK OpenWrt | `./secubox-tools/local-build.sh build` | --- -## 🚀 Quick Start: CrĂ©er un Nouveau Module +## ⚠ RĂšgles Critiques (À TOUJOURS Respecter) -### Étape 1: PrĂ©paration +### 1. RPCD Script Naming +``` +RÈGLE: Nom fichier RPCD = objet ubus (EXACT!) + +✅ CORRECT: +JavaScript: object: 'luci.system-hub' +Fichier: root/usr/libexec/rpcd/luci.system-hub + +❌ INCORRECT (cause -32000 error): +Fichier: root/usr/libexec/rpcd/system-hub +``` + +### 2. Menu Path Matching +``` +RÈGLE: Path menu = fichier vue (EXACT!) + +✅ CORRECT: +Menu JSON: "path": "system-hub/overview" +Fichier: view/system-hub/overview.js + +❌ INCORRECT (cause 404 error): +Menu: "system-hub/overview" +File: view/systemhub/overview.js +``` + +### 3. Permissions ```bash -# Assure-toi d'ĂȘtre dans le bon rĂ©pertoire -cd /home/reepost/CyberMindStudio/_files/secubox +# RPCD scripts = exĂ©cutable +chmod 755 root/usr/libexec/rpcd/luci.* -# Lis le guide d'implĂ©mentation -cat .claude/module-implementation-guide.md +# CSS/JS = lecture seule +chmod 644 htdocs/**/*.{css,js} ``` -### Étape 2: RĂ©diger les SpĂ©cifications - -Ouvre `module-implementation-guide.md` et copie le template. Remplis: - -**Obligatoire**: -- Nom du module -- CatĂ©gorie (Security/Network/System/Performance/Services) -- Description et cas d'utilisation -- 3-5 fonctionnalitĂ©s principales -- MĂ©thodes RPCD (minimum 5-8) -- Configuration UCI -- Views JavaScript (minimum 2-3) - -**RecommandĂ©**: -- DĂ©pendances systĂšme -- SpĂ©cifications de parsing CLI -- Gestion d'erreurs -- Notes de sĂ©curitĂ© - -### Étape 3: Soumettre Ă  Claude - -``` -[Colle ton prompt basĂ© sur le template] -``` - -Claude gĂ©nĂ©rera: -- ✅ Makefile -- ✅ RPCD Backend -- ✅ API Client -- ✅ Views JavaScript -- ✅ Menu JSON -- ✅ ACL JSON -- ✅ README.md - -### Étape 4: Validation - +### 4. Validation OBLIGATOIRE ```bash -# Validation automatique +# TOUJOURS exĂ©cuter avant commit ./secubox-tools/validate-modules.sh +``` -# VĂ©rification syntaxe JavaScript -node -c luci-app-{module}/htdocs/luci-static/resources/**/*.js +### 5. CSS Variables (PAS de hardcode!) +```css +/* ✅ CORRECT */ +color: var(--sh-text-primary); -# Test RPCD sur router (si disponible) -ubus call luci.{module} status +/* ❌ INCORRECT */ +color: #fafafa; +``` + +### 6. Dark Mode (Support OBLIGATOIRE) +```css +/* TOUJOURS fournir styles dark mode */ +[data-theme="dark"] .my-component { + background: var(--sh-bg-card); +} +``` + +### 7. Typographie +```css +/* Texte gĂ©nĂ©ral */ +font-family: 'Inter', sans-serif; + +/* Valeurs numĂ©riques, IDs, code */ +font-family: 'JetBrains Mono', monospace; +``` + +### 8. Gradients +```css +/* Utiliser les variables pour dĂ©gradĂ©s */ +background: linear-gradient(135deg, var(--sh-primary), var(--sh-primary-end)); ``` --- -## 📖 Exemples d'Utilisation +## 🎹 Design System v0.3.0 -### Exemple 1: Module Simple (Monitoring) +InspirĂ© de: https://cybermind.fr/apps/system-hub/demo.html -```markdown -## Nouveau Module SecuBox: System Monitor - -**Nom**: luci-app-system-monitor -**CatĂ©gorie**: System -**Description**: Monitoring temps rĂ©el CPU, RAM, Disk, Network - -### FonctionnalitĂ©s: -1. MĂ©triques systĂšme (CPU%, RAM%, Disk%, Temp) -2. Graphiques temps rĂ©el (5min, 1h, 24h) -3. Alertes configurables (seuils) -4. Export donnĂ©es (CSV, JSON) - -### MĂ©thodes RPCD: -- status -- get_metrics -- get_history -- set_alert -- list_alerts - -[... reste du template ...] +### Palette Dark Mode (RecommandĂ©) +```css +--sh-bg-primary: #0a0a0f; /* Fond principal (noir profond) */ +--sh-bg-secondary: #12121a; /* Fond cartes/sections */ +--sh-bg-tertiary: #1a1a24; /* Fond hover/actif */ +--sh-border: #2a2a35; /* Bordures */ +--sh-primary: #6366f1; /* Indigo */ +--sh-primary-end: #8b5cf6; /* Violet (dĂ©gradĂ©s) */ +--sh-success: #22c55e; /* Vert */ +--sh-danger: #ef4444; /* Rouge */ +--sh-warning: #f59e0b; /* Orange */ ``` -### Exemple 2: Module Complexe (KSM Manager) +### Components CSS (Classes principales) +```css +.sh-page-header /* En-tĂȘte page avec gradient title */ +.sh-page-title /* Titre avec effet gradient text */ +.sh-stat-badge /* Badge stat (130px min) */ +.sh-card /* Carte avec bordure gradient hover */ +.sh-btn-primary /* Bouton gradient indigo-violet */ +.sh-filter-tab /* Onglet de filtre */ +.sh-nav-tab /* Onglet navigation sticky */ +``` -Voir l'exemple complet dans `module-implementation-guide.md` → Section "Exemple Concret: Module KSM" +### Grid Sizes +```css +/* Stats compacts */ +grid-template-columns: repeat(auto-fit, minmax(130px, 1fr)); -22 mĂ©thodes RPCD, 8 views, support HSM hardware, gestion certificats, audit logs, etc. +/* Metrics moyens */ +grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); -### Exemple 3: Module IntĂ©gration (Home Assistant) - -```markdown -## Nouveau Module SecuBox: Home Assistant Bridge - -**Nom**: luci-app-hass-bridge -**CatĂ©gorie**: Services -**Description**: IntĂ©gration bidirectionnelle avec Home Assistant - -### FonctionnalitĂ©s: -1. Auto-discovery MQTT -2. Entities SecuBox → HASS (sensors, switches) -3. Services HASS → SecuBox (actions) -4. Webhooks bidirectionnels -5. Dashboard widgets - -### MĂ©thodes RPCD: -- status -- get_entities -- publish_entity -- trigger_service -- list_webhooks -- add_webhook - -[... reste du template ...] +/* Info cards larges */ +grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); ``` --- -## 🎯 Bonnes Pratiques +## 🚀 Workflow de DĂ©veloppement -### Naming Conventions - -**Package**: -- Format: `luci-app-{nom-module}` (tout en minuscules, tirets) -- Exemples: `luci-app-cdn-cache`, `luci-app-ksm-manager` - -**RPCD Script**: -- **OBLIGATOIRE**: `luci.{nom-module}` (prĂ©fixe `luci.` requis!) -- Emplacement: `/root/usr/libexec/rpcd/luci.{nom-module}` -- Permissions: ExĂ©cutable (`chmod +x`) - -**UCI Config**: -- Fichier: `/etc/config/{nom-module}` (sans `luci-app-`) -- Exemple: `/etc/config/ksm` pour `luci-app-ksm-manager` - -**Views**: -- Emplacement: `/htdocs/luci-static/resources/view/{module}/` -- Fichiers: `overview.js`, `{feature}.js` - -**API Client**: -- Emplacement: `/htdocs/luci-static/resources/{module}/api.js` -- Export: `L.Class.extend({ ... })` - -### Structure Minimale - -**Petit module** (< 5 mĂ©thodes): +### 1. Avant de Commencer +```bash +# Lire les guides +cat ../QUICK-START.md # RĂšgles critiques +cat ../DEVELOPMENT-GUIDELINES.md # Guide complet (section pertinente) ``` -luci-app-{module}/ -├── Makefile -├── README.md + +### 2. DĂ©veloppement +```bash +# Modifier le code +vim luci-app-system-hub/htdocs/luci-static/resources/view/system-hub/overview.js + +# Valider IMMÉDIATEMENT +./secubox-tools/validate-modules.sh +``` + +### 3. Test Local (Optionnel) +```bash +# Build local +./secubox-tools/local-build.sh build luci-app-system-hub + +# VĂ©rifier .ipk +ls -la build/x86-64/luci-app-system-hub*.ipk +``` + +### 4. DĂ©ploiement +```bash +# DĂ©ployer sur routeur de test +./deploy-module-template.sh system-hub + +# Le script fait automatiquement: +# - Backup avec timestamp +# - Deploy JS, CSS, RPCD, menu, ACL +# - Fix permissions +# - Clear cache +# - Restart services +# - VĂ©rification +``` + +### 5. Test Navigateur +``` +1. Ouvrir en MODE PRIVÉ (Ctrl+Shift+N) +2. URL: https://192.168.8.191/cgi-bin/luci/admin/secubox/system/system-hub +3. F12 Console: vĂ©rifier pas d'erreurs +4. F12 Network: tous fichiers 200 OK +5. Tester dark/light mode +6. Tester responsive (mobile view) +``` + +### 6. Commit +```bash +git add . +git commit -m "feat: improve system-hub overview with demo styling" +git push +``` + +--- + +## 🔍 Checklist Pre-Commit + +**OBLIGATOIRE avant chaque commit:** + +- [ ] `./secubox-tools/validate-modules.sh` ✅ PASSED +- [ ] RPCD name = ubus object name +- [ ] Menu path = view file path +- [ ] Permissions: 755 (RPCD), 644 (CSS/JS) +- [ ] JSON files valides (jsonlint) +- [ ] CSS: variables utilisĂ©es (pas hardcode) +- [ ] CSS: dark mode supportĂ© `[data-theme="dark"]` +- [ ] JS: gestion d'erreur sur API calls +- [ ] Version incrĂ©mentĂ©e dans Makefile (PKG_VERSION) + +--- + +## 🐛 Erreurs Communes & Solutions Rapides + +| Erreur | Cause | Solution Rapide | +|--------|-------|-----------------| +| **-32000 Object not found** | RPCD name ≠ ubus object | Renommer: `mv rpcd/wrong-name rpcd/luci.correct-name` | +| **404 View not found** | Menu path ≠ file location | Corriger path dans `menu.d/*.json` | +| **403 Forbidden CSS** | Permissions incorrectes | `chmod 644 *.css` | +| **[object HTMLButtonElement]** | Array imbriquĂ© dans E() | Enlever wrapper: `E('div', {}, renderButtons())` | +| **Styles pas Ă  jour** | Cache navigateur | Mode privĂ© + Ctrl+Shift+R | + +**Pour diagnostics dĂ©taillĂ©s:** Voir [DEVELOPMENT-GUIDELINES.md - Common Errors](../DEVELOPMENT-GUIDELINES.md#common-errors--solutions) + +--- + +## đŸ§Ș Debug Commands + +```bash +# VĂ©rifier ubus objects +ssh root@192.168.8.191 "ubus list | grep luci.system-hub" + +# Tester RPCD directement +ssh root@192.168.8.191 "/usr/libexec/rpcd/luci.system-hub call getHealth" + +# VĂ©rifier fichiers dĂ©ployĂ©s +ssh root@192.168.8.191 "ls -la /www/luci-static/resources/view/system-hub/" + +# VĂ©rifier permissions +ssh root@192.168.8.191 "ls -la /usr/libexec/rpcd/luci.system-hub" + +# Logs systĂšme +ssh root@192.168.8.191 "logread | grep -i error | tail -20" + +# Clear cache + restart +ssh root@192.168.8.191 "rm -f /tmp/luci-indexcache /tmp/luci-modulecache/* && /etc/init.d/rpcd restart && /etc/init.d/uhttpd restart" +``` + +--- + +## 📁 Fichiers de ce RĂ©pertoire (.claude/) + +### Fichiers Actifs (v2.0) + +| Fichier | Description | Status | +|---------|-------------|--------| +| **README.md** | Ce fichier - Guide configuration Claude | ✅ ACTIF v2.0 | +| **settings.local.json** | Configuration locale Claude Code | ✅ ACTIF v2.0 | + +### Fichiers Legacy (Deprecated) + +| Fichier | Description | Migration | +|---------|-------------|-----------| +| `module-prompts.md` | Anciens prompts modules (18KB) | → DEVELOPMENT-GUIDELINES.md | +| `module-implementation-guide.md` | Ancien template modules (23KB) | → DEVELOPMENT-GUIDELINES.md | +| `context.md` | Ancien contexte (13KB) | → README.md + CLAUDE.md | + +**⚠ Les fichiers legacy sont conservĂ©s pour rĂ©fĂ©rence historique mais ne doivent plus ĂȘtre utilisĂ©s.** + +**Utilisez maintenant:** +- DEVELOPMENT-GUIDELINES.md (guide complet) +- QUICK-START.md (aide-mĂ©moire) +- CLAUDE.md (architecture) + +--- + +## 🎯 Templates de Code Rapides + +### Page Header +```javascript +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') + ]), + E('div', { 'class': 'sh-stats-grid' }, [ + E('div', { 'class': 'sh-stat-badge' }, [ + E('div', { 'class': 'sh-stat-value' }, '92'), + E('div', { 'class': 'sh-stat-label' }, 'Score') + ]) + ]) +]) +``` + +### Card with Gradient Border +```javascript +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 here + ]) +]) +``` + +### RPCD Script +```bash +#!/bin/sh +case "$1" in + list) + echo '{"getStatus": {}, "getHealth": {}}' + ;; + call) + case "$2" in + getStatus) + printf '{"enabled": true, "version": "1.0.0"}\n' + ;; + getHealth) + cpu=$(top -bn1 | grep "CPU:" | awk '{print $2}' | sed 's/%//') + printf '{"cpu": {"usage": %s}}\n' "$cpu" + ;; + esac + ;; +esac +``` + +**Pour plus de templates:** Voir [QUICK-START.md - Quick Code Templates](../QUICK-START.md#quick-code-templates) + +--- + +## 📊 Structure d'un Module Type + +``` +luci-app-/ +├── Makefile # Package OpenWrt +├── README.md # Documentation module ├── htdocs/luci-static/resources/ -│ ├── {module}/ -│ │ └── api.js -│ └── view/{module}/ -│ └── overview.js +│ ├── view// +│ │ ├── overview.js # Vue principale +│ │ ├── settings.js # Configuration (optionnel) +│ │ └── *.js # Autres vues +│ └── / +│ ├── api.js # Client RPC +│ ├── common.css # Styles partagĂ©s +│ └── overview.css # Styles page └── root/ - ├── usr/ - │ ├── libexec/rpcd/ - │ │ └── luci.{module} - │ └── share/ - │ ├── luci/menu.d/ - │ │ └── luci-app-{module}.json - │ └── rpcd/acl.d/ - │ └── luci-app-{module}.json - └── etc/config/ - └── {module} (optionnel) -``` - -**Module complet** (> 8 mĂ©thodes): -``` -[Structure minimale +] -├── htdocs/luci-static/resources/ -│ └── view/{module}/ -│ ├── overview.js -│ ├── management.js -│ ├── settings.js -│ └── logs.js -└── root/etc/init.d/ - └── {module} (si besoin d'un daemon) -``` - -### Checklist PrĂ©-Commit - -Avant de commiter un nouveau module: - -- [ ] `./secubox-tools/validate-modules.sh` passe ✅ -- [ ] Tous les fichiers JavaScript valident avec `node -c` -- [ ] Tous les JSON valident avec `jsonlint` -- [ ] RPCD script est exĂ©cutable -- [ ] Nom RPCD = `luci.{module}` (avec prĂ©fixe!) -- [ ] README.md complet avec installation/usage -- [ ] Makefile a toutes les dĂ©pendances -- [ ] ACL contient toutes les mĂ©thodes RPCD -- [ ] Menu paths matchent les fichiers view -- [ ] Git commit message descriptif - ---- - -## 🔧 Outils de DĂ©veloppement - -### Validation Automatique - -```bash -# Validation complĂšte de tous les modules -./secubox-tools/validate-modules.sh - -# Validation d'un module spĂ©cifique -./secubox-tools/secubox-debug.sh luci-app-{module} - -# RĂ©paration automatique des problĂšmes courants -./secubox-tools/secubox-repair.sh -``` - -### Build Local - -```bash -# Build tous les packages -./secubox-tools/local-build.sh build - -# Build un package spĂ©cifique -./secubox-tools/local-build.sh build luci-app-{module} - -# Build pour architecture spĂ©cifique -./secubox-tools/local-build.sh build --arch aarch64-cortex-a72 - -# Validation seule (rapide) -./secubox-tools/local-build.sh validate -``` - -### Test sur Router - -```bash -# Transfer IPK -scp build/x86-64/luci-app-{module}_*.ipk root@192.168.1.1:/tmp/ - -# Install sur router -ssh root@192.168.1.1 -opkg install /tmp/luci-app-{module}_*.ipk -/etc/init.d/rpcd restart -/etc/init.d/uhttpd restart - -# Test RPC manuel -ubus list | grep {module} -ubus call luci.{module} status + ├── usr/libexec/rpcd/ + │ └── luci. ⚠ MUST match ubus object! + └── usr/share/ + ├── luci/menu.d/ + │ └── luci-app-.json + └── rpcd/acl.d/ + └── luci-app-.json ``` --- -## 📚 Ressources - -### Documentation OpenWrt/LuCI - -- [LuCI API Reference](https://openwrt.github.io/luci/) -- [UCI Configuration](https://openwrt.org/docs/guide-user/base-system/uci) -- [RPCD Guide](https://openwrt.org/docs/techref/rpcd) -- [OpenWrt Packages](https://openwrt.org/packages/start) - -### Exemples de Code - -Tous les 14 modules SecuBox existants servent de rĂ©fĂ©rence: - -**Simples** (bon pour dĂ©buter): -- `luci-app-netdata-dashboard` - Iframe simple + contrĂŽles -- `luci-app-network-modes` - Preset application - -**Moyens**: -- `luci-app-bandwidth-manager` - QoS avec graphiques -- `luci-app-media-flow` - DĂ©tection + stats - -**AvancĂ©s** (patterns complexes): -- `luci-app-wireguard-dashboard` - GĂ©nĂ©ration clĂ©s + QR codes -- `luci-app-auth-guardian` - OAuth + vouchers + sessions -- `luci-app-traffic-shaper` - TC/CAKE intĂ©gration - -### Architecture SecuBox +## 🌐 URLs de Test +**System Hub (Design v0.3.0):** ``` -SecuBox Hub (luci-app-secubox) - ├── Security Layer - │ ├── CrowdSec Dashboard - │ └── Auth Guardian - ├── Network Layer - │ ├── Network Modes - │ ├── Client Guardian - │ ├── WireGuard Dashboard - │ └── VHost Manager - ├── Traffic Layer - │ ├── Bandwidth Manager - │ ├── Media Flow - │ ├── CDN Cache - │ └── Traffic Shaper - ├── Monitoring Layer - │ ├── Netdata Dashboard - │ ├── Netifyd Dashboard - │ └── System Hub - └── [Nouveau Module] +https://192.168.8.191/cgi-bin/luci/admin/secubox/system/system-hub ``` +**SecuBox Dashboard:** +``` +https://192.168.8.191/cgi-bin/luci/admin/secubox +``` + +**⚠ TOUJOURS tester en mode privĂ©/incognito** aprĂšs dĂ©ploiement! + --- -## đŸ€ Contribution +## 📚 Liens Utiles -### Workflow Git - -```bash -# CrĂ©er branche pour nouveau module -git checkout -b feature/luci-app-{module} - -# DĂ©velopper avec validation continue -# ... dĂ©veloppement ... -./secubox-tools/validate-modules.sh - -# Commit -git add luci-app-{module}/ -git commit -m "feat: implement {Module Name} - {brief description}" - -# Push -git push origin feature/luci-app-{module} - -# Tag pour release -git tag v0.0.X -git push origin v0.0.X -``` - -### Format de Commit Messages - -``` -feat: implement KSM Manager - hardware key storage with Nitrokey -fix: correct RPCD method naming in CDN Cache -docs: add installation guide for Traffic Shaper -chore: update dependencies for Bandwidth Manager -refactor: improve error handling in Auth Guardian -``` +- **DĂ©mo design:** https://cybermind.fr/apps/system-hub/demo.html +- **OpenWrt LuCI:** https://github.com/openwrt/luci +- **OpenWrt Docs:** https://openwrt.org/docs/ +- **Issues Claude Code:** https://github.com/anthropics/claude-code/issues --- -## 💡 Support +## 📝 Changelog Configuration -### Debug Module Issues +### Version 2.0.0 (2025-12-26) -**ProblĂšme**: Module n'apparaĂźt pas dans le menu -- VĂ©rifier menu JSON path -- VĂ©rifier ACL permissions -- RedĂ©marrer uhttpd: `/etc/init.d/uhttpd restart` +**Ajouts majeurs:** +- ✅ DEVELOPMENT-GUIDELINES.md (33KB, guide complet) +- ✅ QUICK-START.md (6.4KB, aide-mĂ©moire) +- ✅ deploy-module-template.sh (script standardisĂ©) +- ✅ Design System v0.3.0 (demo-inspired) +- ✅ 8 rĂšgles critiques documentĂ©es +- ✅ Checklists validation complĂštes +- ✅ Templates de code prĂȘts Ă  l'emploi -**ProblĂšme**: RPC errors "Object not found" -- VĂ©rifier nom RPCD = `luci.{module}` -- VĂ©rifier RPCD exĂ©cutable: `chmod +x` -- RedĂ©marrer rpcd: `/etc/init.d/rpcd restart` -- Tester ubus: `ubus list | grep {module}` +**DĂ©prĂ©ciations:** +- ⚠ module-prompts.md → migrĂ© vers DEVELOPMENT-GUIDELINES.md +- ⚠ module-implementation-guide.md → migrĂ© vers DEVELOPMENT-GUIDELINES.md +- ⚠ context.md → migrĂ© vers README.md + CLAUDE.md -**ProblĂšme**: JavaScript errors -- Valider syntaxe: `node -c {file}.js` -- VĂ©rifier imports: `'require {module}/api'` -- Check console browser (F12) +**AmĂ©liorations:** +- ✅ Documentation structurĂ©e en 4 guides principaux +- ✅ Workflow de dĂ©veloppement clarifiĂ© +- ✅ Erreurs communes documentĂ©es avec solutions +- ✅ Script de dĂ©ploiement avec backup automatique -**ProblĂšme**: Build failures -- VĂ©rifier Makefile dependencies -- VĂ©rifier include path: `../../luci.mk` -- Clean build: `make clean` +### Version 1.0.0 (2023-12-23) -### Demander de l'Aide - -Ouvre une issue GitHub avec: -1. Nom du module -2. Description du problĂšme -3. Logs d'erreur -4. Output de `./secubox-tools/validate-modules.sh` -5. Configuration (anonymisĂ©e si nĂ©cessaire) +- Version initiale avec module-prompts.md et module-implementation-guide.md --- -## 🎉 Success Stories - -Modules dĂ©jĂ  implĂ©mentĂ©s avec succĂšs: - -1. **WireGuard Dashboard** - GĂ©nĂ©ration peers + QR codes -2. **Auth Guardian** - OAuth + vouchers complĂšte -3. **Bandwidth Manager** - QoS avec graphiques temps rĂ©el -4. **Media Flow** - DĂ©tection streaming avec donut chart -5. **CDN Cache** - Hit ratio gauge + cache management -6. **Traffic Shaper** - TC/CAKE avec presets - -Tous validĂ©s ✅ et production-ready 🚀 - ---- - -**Bon dĂ©veloppement avec SecuBox!** 🔧🔐🌐 +**DerniĂšre mise Ă  jour:** 2025-12-26 +**Maintenu par:** CyberMind Studio +**Version:** 2.0.0 diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 2f993cf..2efff09 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -100,7 +100,21 @@ "Bash(luci-app-system-hub/htdocs/luci-static/resources/view/system-hub/*.js)", "Bash(luci-app-system-hub/root/usr/libexec/rpcd/luci.system-hub )", "Bash(luci-app-system-hub/root/usr/share/rpcd/acl.d/luci-app-system-hub.json )", - "Bash(LUCI_DEVELOPMENT_REFERENCE.md)" + "Bash(LUCI_DEVELOPMENT_REFERENCE.md)", + "Bash(while read file)", + "Bash(do echo \"=== $file ===\" grep \"object:\" \"$file\")", + "Bash(for file in luci-app-secubox/htdocs/luci-static/resources/view/secubox/*.js luci-app-secubox/htdocs/luci-static/resources/secubox/*.js)", + "Bash(./deploy-secubox-fix.sh)", + "Bash(./deploy-modules-v2.sh:*)", + "Bash(./deploy-dynamic-modules.sh:*)", + "Bash(/tmp/force-reload-luci.sh:*)", + "Bash(/tmp/deploy-common-css.sh)", + "Bash(/tmp/deploy-services.sh)", + "Bash(/tmp/deploy-health.sh)", + "Bash(/tmp/deploy-system-hub-all.sh)", + "Bash(/tmp/deploy-secubox-final.sh)", + "WebFetch(domain:cybermind.fr)", + "Bash(/tmp/deploy-system-hub-demo-style.sh)" ] } } diff --git a/CLAUDE.md b/CLAUDE.md index eb8e9d1..664c3df 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -2,6 +2,45 @@ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. +## 📚 Documentation Index + +**IMPORTANT:** Before working on any code, consult these guides: + +1. **[DEVELOPMENT-GUIDELINES.md](./DEVELOPMENT-GUIDELINES.md)** - ⭐ **GUIDE COMPLET** + - Design System & UI Guidelines (palettes, typographie, composants) + - Architecture & Naming Conventions (RPCD, menu paths, prefixes) + - RPCD & ubus Best Practices (erreurs communes, solutions) + - ACL & Permissions (templates, validations) + - JavaScript Patterns (API modules, views, event handling) + - CSS/Styling Standards (variables, responsive, dark mode) + - Common Errors & Solutions (diagnostics, fixes) + - Validation Checklist (pre-commit, pre-deploy, post-deploy) + - Deployment Procedures (scripts, rollback, versioning) + +2. **[QUICK-START.md](./QUICK-START.md)** - ⚡ **AIDE-MÉMOIRE RAPIDE** + - RĂšgles critiques (RPCD naming, menu paths, permissions) + - Design system essentials (couleurs, fonts, classes) + - Common commands (validation, build, deploy, debug) + - Quick code templates (RPCD, View, Headers, Cards) + - Error quick fixes + +3. **CLAUDE.md** (ce fichier) - đŸ—ïž **ARCHITECTURE & BUILD** + - Build commands (OpenWrt SDK, local build) + - Module structure (files, directories) + - CI/CD workflows + - Common issues techniques + +**⚠ RÈGLES CRITIQUES À TOUJOURS RESPECTER:** + +1. **RPCD Script Naming:** Nom fichier = objet ubus (`luci.system-hub`) +2. **Menu Path Matching:** Path menu = fichier vue (`system-hub/overview.js`) +3. **Permissions:** RPCD = 755, CSS/JS = 644 +4. **Validation:** Toujours exĂ©cuter `./secubox-tools/validate-modules.sh` avant commit +5. **CSS Variables:** Toujours utiliser `var(--sh-*)`, jamais hardcoder les couleurs +6. **Dark Mode:** Toujours supporter dark mode avec `[data-theme="dark"]` +7. **Typography:** Inter (texte), JetBrains Mono (valeurs numĂ©riques) +8. **Gradient Effects:** Utiliser `--sh-primary` → `--sh-primary-end` pour dĂ©gradĂ©s + ## Project Overview SecuBox is a comprehensive security and network management suite for OpenWrt. The repository contains 13 LuCI application packages that provide dashboards for security monitoring, network intelligence, access control, bandwidth management, and system administration. diff --git a/DEVELOPMENT-GUIDELINES.md b/DEVELOPMENT-GUIDELINES.md new file mode 100644 index 0000000..c44dec8 --- /dev/null +++ b/DEVELOPMENT-GUIDELINES.md @@ -0,0 +1,1443 @@ +# SecuBox & System Hub - Development Guidelines + +**Version:** 1.0.0 +**Date:** 2025-12-26 +**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](#design-system--ui-guidelines) +2. [Architecture & Naming Conventions](#architecture--naming-conventions) +3. [RPCD & ubus Best Practices](#rpcd--ubus-best-practices) +4. [ACL & Permissions](#acl--permissions) +5. [JavaScript Patterns](#javascript-patterns) +6. [CSS/Styling Standards](#cssstyling-standards) +7. [Common Errors & Solutions](#common-errors--solutions) +8. [Validation Checklist](#validation-checklist) +9. [Deployment Procedures](#deployment-procedures) + +--- + +## Design System & UI Guidelines + +### Color Palette (Demo-inspired) + +**IMPORTANT:** Toujours utiliser la palette dĂ©finie dans `system-hub/common.css` + +#### Dark Mode (Primary - Recommended) +```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) +```css +--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 +```css +/* 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): +```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 +```css +/* 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 + +#### 1. Page Header (Standard) + +**HTML Structure:** +```javascript +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:** +- `.sh-page-header` - Container with flex layout +- `.sh-page-title` - Gradient text effect +- `.sh-page-title-icon` - Icon (no gradient) +- `.sh-page-subtitle` - Secondary text +- `.sh-stats-grid` - Grid pour badges (130px min) + +#### 2. Stats Badges + +**RÈGLE:** Minimum 130px, police monospace pour valeurs + +```javascript +E('div', { 'class': 'sh-stat-badge' }, [ + E('div', { 'class': 'sh-stat-value' }, '92'), + E('div', { 'class': 'sh-stat-label' }, 'CPU %') +]) +``` + +**Grid Layout:** +```css +.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 + +```javascript +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:** +- `.sh-card` - Bordure gradient (visible au hover) +- `.sh-card-success` - Bordure verte permanente +- `.sh-card-danger` - Bordure rouge permanente +- `.sh-card-warning` - Bordure orange permanente + +#### 4. Buttons + +**Gradient buttons (preferred):** +```javascript +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:** +- Shadow effect (dĂ©jĂ  dans CSS) +- Hover animation (translateY(-2px)) +- Transition smooth (0.3s cubic-bezier) + +#### 5. Filter Tabs + +```javascript +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:** +- Background: gradient indigo-violet +- Color: white +- Box-shadow avec glow + +### Grid Systems + +#### Stats Overview (Compact) +```css +grid-template-columns: repeat(auto-fit, minmax(130px, 1fr)); +gap: 16px; +``` + +#### Metric Cards (Medium) +```css +grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); +gap: 20px; +``` + +#### Info Cards (Large) +```css +grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); +gap: 20px; +``` + +### Gradient Effects + +#### Gradient Text (Titles) +```css +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) +```css +background: linear-gradient(135deg, var(--sh-primary), var(--sh-primary-end)); +``` + +#### Gradient Borders (Top) +```css +/* 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 +```css +transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); +transform: translateY(-3px); /* Cards */ +transform: translateY(-2px); /* Buttons, badges */ +``` + +#### Shadow Progression +```css +/* 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 + +### 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:** +```javascript +var callStatus = rpc.declare({ + object: 'luci.system-hub', // ← Nom objet + method: 'getHealth' +}); +``` + +**Fichier RPCD:** +```bash +root/usr/libexec/rpcd/luci.system-hub # ← EXACT MATCH +``` + +#### ❌ INCORRECT (Causes d'erreur -32000): + +```bash +# 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 +``` + +### Menu Path Conventions + +**RÈGLE:** Les chemins dans menu.d/*.json doivent correspondre EXACTEMENT aux fichiers de vue. + +#### ✅ CORRECT: + +**Menu JSON:** +```json +{ + "action": { + "type": "view", + "path": "system-hub/overview" + } +} +``` + +**Fichier de vue:** +```bash +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-/ +├── Makefile +├── README.md +├── htdocs/luci-static/resources/ +│ ├── view// +│ │ ├── overview.js # Page principale +│ │ ├── settings.js # Configuration +│ │ └── *.js # Autres vues +│ └── / +│ ├── api.js # RPC client +│ ├── theme.js # Theme helpers (optionnel) +│ ├── common.css # Styles partagĂ©s +│ └── *.css # Styles spĂ©cifiques +└── root/ + ├── usr/ + │ ├── libexec/rpcd/ + │ │ └── luci. # ⚠ MUST match ubus object + │ └── share/ + │ ├── luci/menu.d/ + │ │ └── luci-app-.json + │ └── rpcd/acl.d/ + │ └── luci-app-.json + └── etc/config/ (optionnel) +``` + +--- + +## RPCD & ubus Best Practices + +### RPCD Script Template (Shell) + +**Fichier:** `root/usr/libexec/rpcd/luci.` + +```bash +#!/bin/sh +# RPCD backend for +# ubus object: luci. + +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.` +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:** + +```bash +# 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:** +```bash +# 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:** +```bash +# 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:** +```bash +# 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-.json` + +```json +{ + "luci-app-": { + "description": "Grant access to ", + "read": { + "ubus": { + "luci.": [ + "getStatus", + "getHealth", + "getServices" + ] + }, + "uci": [ + "" + ] + }, + "write": { + "ubus": { + "luci.": [ + "setConfig", + "restartService" + ] + }, + "uci": [ + "" + ] + } + } +} +``` + +### 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:** +```json +{ + "read": { + "ubus": { + "luci.system-hub": [ + "getHealth" // ← Ajouter la mĂ©thode manquante + ] + } + } +} +``` + +#### Error: "UCI config not accessible" + +**Cause:** Config UCI pas dans ACL + +**Solution:** +```json +{ + "read": { + "uci": [ + "system-hub" // ← Ajouter le config + ] + } +} +``` + +--- + +## JavaScript Patterns + +### API Module Template + +**Fichier:** `htdocs/luci-static/resources//api.js` + +```javascript +'use strict'; +'require rpc'; +'require uci'; + +return L.Class.extend({ + // DĂ©clarer les appels RPC + callGetStatus: rpc.declare({ + object: 'luci.', + method: 'getStatus', + expect: { } + }), + + callGetHealth: rpc.declare({ + object: 'luci.', + 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//overview.js` + +```javascript +'use strict'; +'require view'; +'require ui'; +'require dom'; +'require poll'; +'require /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': '-dashboard' }, [ + // Link CSS files + E('link', { 'rel': 'stylesheet', 'href': L.resource('/common.css') }), + E('link', { 'rel': 'stylesheet', 'href': L.resource('/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 + +```javascript +// ✅ 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 + +```javascript +// ❌ 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 + +```javascript +// ❌ 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 + +```javascript +// ❌ 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 + +``` +/ +├── 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 + +```css +/** + * 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 + +```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 + +```css +/* 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 + +```css +/* 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 + +```css +/* 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:** + +```css +/* 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:** + +```css +--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:** +```bash +# 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:** +```bash +# 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:** +```bash +# VĂ©rifier permissions +ls -la /www/luci-static/resources/system-hub/common.css +``` + +**Solution:** +```bash +# 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:** +```bash +# 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:** +```bash +# ❌ 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:** +- Changements CSS/JS non visibles +- Anciennes donnĂ©es affichĂ©es +- Code mis Ă  jour mais interface identique + +**Solutions:** +```bash +# 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:** +```bash +# 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: + +- [ ] **RPCD Script:** + - [ ] Nom fichier correspond Ă  objet ubus + - [ ] ExĂ©cutable (chmod +x) + - [ ] Structure list/call correcte + - [ ] Retourne JSON valide + - [ ] Toutes mĂ©thodes implĂ©mentĂ©es + +- [ ] **Menu & ACL:** + - [ ] Path menu correspond au fichier vue + - [ ] ACL liste toutes les mĂ©thodes ubus + - [ ] JSON valide (jsonlint) + +- [ ] **JavaScript:** + - [ ] 'use strict' en premiĂšre ligne + - [ ] Imports requis prĂ©sents + - [ ] Pas de console.log en prod + - [ ] Gestion d'erreur sur API calls + - [ ] Event handlers bindĂ©s correctement + +- [ ] **CSS:** + - [ ] Variables CSS utilisĂ©es (pas de hardcode) + - [ ] Classes prefixĂ©es (sh-, sb-, module-) + - [ ] Dark mode supportĂ© + - [ ] Responsive (max-width: 768px) + - [ ] Transitions cohĂ©rentes + +- [ ] **Makefile:** + - [ ] PKG_VERSION incrĂ©mentĂ© + - [ ] LUCI_DEPENDS correct + - [ ] Include path correct (../../luci.mk) + +### Pre-Deploy Checklist + +Avant dĂ©ploiement sur routeur: + +- [ ] **Validation scripts:** + ```bash + ./secubox-tools/validate-modules.sh + ``` + +- [ ] **Test RPCD local:** + ```bash + /usr/libexec/rpcd/luci.module-name list + /usr/libexec/rpcd/luci.module-name call getStatus + ``` + +- [ ] **Test JSON:** + ```bash + find . -name "*.json" -exec jsonlint {} \; + ``` + +- [ ] **Shellcheck:** + ```bash + shellcheck root/usr/libexec/rpcd/* + ``` + +- [ ] **Permissions:** + ```bash + # RPCD scripts + chmod 755 root/usr/libexec/rpcd/* + + # CSS/JS files + chmod 644 htdocs/luci-static/resources/**/* + ``` + +### Post-Deploy Checklist + +AprĂšs dĂ©ploiement: + +- [ ] **Services:** + ```bash + /etc/init.d/rpcd status + /etc/init.d/uhttpd status + ``` + +- [ ] **ubus objects:** + ```bash + ubus list | grep luci.module-name + ``` + +- [ ] **Fichiers prĂ©sents:** + ```bash + ls -la /www/luci-static/resources/view/module-name/ + ls -la /www/luci-static/resources/module-name/ + ``` + +- [ ] **Permissions correctes:** + ```bash + ls -la /usr/libexec/rpcd/luci.module-name + ls -la /www/luci-static/resources/module-name/*.css + ``` + +- [ ] **Test navigateur:** + - [ ] Ouvrir en mode privĂ© + - [ ] VĂ©rifier console (F12) - pas d'erreurs + - [ ] VĂ©rifier Network tab - tous les fichiers chargent (200) + - [ ] Tester dark/light mode + - [ ] Tester responsive (mobile view) + +--- + +## Deployment Procedures + +### Standard Deployment Script Template + +```bash +#!/bin/bash +# Deploy + +ROUTER="root@192.168.8.191" +MODULE="" +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: + +```bash +#!/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 +# Makefile +PKG_VERSION:=0.3.0 +PKG_RELEASE:=1 +``` + +```css +/* CSS files */ +/** + * Module - Styles + * Version: 0.3.0 + */ +``` + +```javascript +// JavaScript +// Version: 0.3.0 +``` + +**Semantic Versioning:** +- MAJOR.MINOR.PATCH (1.2.3) +- MAJOR: Breaking changes +- MINOR: New features (backward compatible) +- PATCH: Bug fixes + +--- + +## Quick Reference + +### Essential Commands + +```bash +# 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 + +```css +/* 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 + +```css +/* 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 */ +``` + +--- + +## 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:** +- CLAUDE.md - Architecture et build +- secubox-tools/validate-modules.sh - Validation automatique +- Templates/ - Templates de code + +--- + +**DerniĂšre mise Ă  jour:** 2025-12-26 +**Maintenu par:** CyberMind Studio +**Version du guide:** 1.0.0 diff --git a/QUICK-START.md b/QUICK-START.md new file mode 100644 index 0000000..0e28e23 --- /dev/null +++ b/QUICK-START.md @@ -0,0 +1,284 @@ +# Quick Start Guide - SecuBox Development + +**⚡ Aide-mĂ©moire rapide pour dĂ©veloppement** + +Pour le guide complet, voir [DEVELOPMENT-GUIDELINES.md](./DEVELOPMENT-GUIDELINES.md) + +--- + +## ⚠ RÈGLES CRITIQUES (À NE JAMAIS OUBLIER) + +### 1. RPCD Script Naming +```bash +# Le nom DOIT correspondre EXACTEMENT Ă  l'objet ubus +JavaScript: object: 'luci.system-hub' +Fichier: root/usr/libexec/rpcd/luci.system-hub ✅ + +# Sinon: Error -32000 "Object not found" +``` + +### 2. Menu Path Matching +```json +Menu JSON: "path": "system-hub/overview" +Fichier: view/system-hub/overview.js ✅ + +# Sinon: HTTP 404 Not Found +``` + +### 3. Permissions Files +```bash +# RPCD scripts = exĂ©cutable +chmod 755 root/usr/libexec/rpcd/luci.* + +# CSS/JS = lecture seule +chmod 644 htdocs/**/*.{css,js} + +# Sinon: 403 Forbidden ou script non exĂ©cutĂ© +``` + +--- + +## 🎹 Design System Essentials + +### Color Palette (Dark Mode) +```css +--sh-bg-primary: #0a0a0f; /* Fond principal */ +--sh-bg-card: #12121a; /* Cartes */ +--sh-border: #2a2a35; /* Bordures */ +--sh-primary: #6366f1; /* Indigo */ +--sh-primary-end: #8b5cf6; /* Violet */ +``` + +### Fonts +```css +/* GĂ©nĂ©ral */ +font-family: 'Inter', sans-serif; + +/* Valeurs numĂ©riques */ +font-family: 'JetBrains Mono', monospace; +``` + +### Component Classes +```css +.sh-page-header /* Page header */ +.sh-page-title /* Title (gradient text) */ +.sh-stat-badge /* Stat badge (130px min) */ +.sh-card /* Card (gradient border on hover) */ +.sh-btn-primary /* Button (gradient) */ +.sh-filter-tab /* Filter tab */ +``` + +### Grid Sizes +```css +/* Stats */ +grid-template-columns: repeat(auto-fit, minmax(130px, 1fr)); + +/* Metrics */ +grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); + +/* Info cards */ +grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); +``` + +--- + +## 🔧 Common Commands + +### Validation +```bash +# Valider TOUT avant commit +./secubox-tools/validate-modules.sh + +# JSON +jsonlint file.json + +# Shell +shellcheck root/usr/libexec/rpcd/* +``` + +### Build +```bash +# Build local +./secubox-tools/local-build.sh build luci-app-module-name + +# Build OpenWrt SDK +make package/luci-app-module-name/compile V=s +``` + +### Deploy +```bash +# Copier fichiers +scp file.js root@192.168.8.191:/www/luci-static/resources/ + +# Fix permissions +ssh root@192.168.8.191 "chmod 644 /www/luci-static/resources/**/*.css" + +# Clear cache + restart +ssh root@192.168.8.191 "rm -f /tmp/luci-indexcache /tmp/luci-modulecache/* && /etc/init.d/rpcd restart && /etc/init.d/uhttpd restart" +``` + +### Debug +```bash +# Test RPCD +ssh root@router "ubus list | grep luci.module" +ssh root@router "ubus call luci.module-name getStatus" + +# Check files +ssh root@router "ls -la /www/luci-static/resources/view/module-name/" + +# Logs +ssh root@router "logread | grep -i error" +``` + +--- + +## 🚹 Common Errors & Quick Fixes + +| Error | Quick Fix | +|-------|-----------| +| **-32000 Object not found** | Rename RPCD file to match ubus object | +| **404 View not found** | Fix menu path to match file location | +| **403 Forbidden CSS** | `chmod 644 *.css` | +| **[object HTMLButtonElement]** | Remove array wrapper: `E('div', {}, renderButtons())` | +| **Styles not updating** | Clear browser cache (Ctrl+Shift+R) + mode privĂ© | + +--- + +## 📋 Pre-Commit Checklist + +- [ ] `./secubox-tools/validate-modules.sh` ✅ +- [ ] RPCD name = ubus object name +- [ ] Menu path = view file path +- [ ] Permissions: 755 (RPCD), 644 (CSS/JS) +- [ ] JSON valide (jsonlint) +- [ ] CSS: variables utilisĂ©es (pas hardcode) +- [ ] CSS: dark mode supportĂ© +- [ ] JS: gestion d'erreur sur API calls +- [ ] Version incrĂ©mentĂ©e (PKG_VERSION) + +--- + +## 📁 File Structure Template + +``` +luci-app-/ +├── Makefile +├── htdocs/luci-static/resources/ +│ ├── view// +│ │ └── overview.js +│ └── / +│ ├── api.js +│ ├── common.css +│ └── overview.css +└── root/ + ├── usr/libexec/rpcd/ + │ └── luci. ⚠ MUST match ubus object! + └── usr/share/ + ├── luci/menu.d/ + │ └── luci-app-.json + └── rpcd/acl.d/ + └── luci-app-.json +``` + +--- + +## 🎯 Quick Code Templates + +### RPCD Script +```bash +#!/bin/sh +case "$1" in + list) + echo '{"getStatus": {}, "getHealth": {}}' + ;; + call) + case "$2" in + getStatus) + printf '{"enabled": true}\n' + ;; + esac + ;; +esac +``` + +### View (JavaScript) +```javascript +'use strict'; +'require view'; +'require /api as API'; + +return view.extend({ + load: function() { + return API.getStatus(); + }, + render: function(data) { + return E('div', { 'class': 'sh-page-header' }, [ + E('h2', { 'class': 'sh-page-title' }, 'Title') + ]); + }, + handleSaveApply: null, + handleSave: null, + handleReset: null +}); +``` + +### Page Header +```javascript +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') + ]), + E('div', { 'class': 'sh-stats-grid' }, [ + E('div', { 'class': 'sh-stat-badge' }, [ + E('div', { 'class': 'sh-stat-value' }, '92'), + E('div', { 'class': 'sh-stat-label' }, 'Score') + ]) + ]) +]) +``` + +### Card with Gradient Border +```javascript +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 + ]) +]) +``` + +--- + +## 🌐 Test URLs + +``` +SecuBox Dashboard: +https://192.168.8.191/cgi-bin/luci/admin/secubox + +System Hub: +https://192.168.8.191/cgi-bin/luci/admin/secubox/system/system-hub +``` + +**TOUJOURS tester en mode privĂ©** (Ctrl+Shift+N) aprĂšs deploy! + +--- + +## 📚 Documentation + +- **Guide complet:** [DEVELOPMENT-GUIDELINES.md](./DEVELOPMENT-GUIDELINES.md) +- **Architecture:** [CLAUDE.md](./CLAUDE.md) +- **Validation:** `./secubox-tools/validate-modules.sh` +- **DĂ©mo design:** https://cybermind.fr/apps/system-hub/demo.html + +--- + +**Version:** 1.0.0 | **Date:** 2025-12-26 diff --git a/README.md b/README.md index c302b22..438f31e 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,30 @@ [![Test & Validate](https://github.com/gkerma/secubox/actions/workflows/test-validate.yml/badge.svg)](https://github.com/gkerma/secubox/actions/workflows/test-validate.yml) [![License](https://img.shields.io/badge/License-Apache%202.0-green.svg)](LICENSE) +## 📚 Documentation pour DĂ©veloppeurs + +**NOUVEAU (2025-12-26):** Guides complets de dĂ©veloppement disponibles! + +| Guide | Description | Public | +|-------|-------------|--------| +| **[DEVELOPMENT-GUIDELINES.md](./DEVELOPMENT-GUIDELINES.md)** | ⭐ Guide complet: Design System, RPCD/ubus, ACL, JavaScript, CSS, Debugging (100+ pages) | DĂ©veloppeurs, IA assistants | +| **[QUICK-START.md](./QUICK-START.md)** | ⚡ Aide-mĂ©moire rapide: RĂšgles critiques, commandes, templates de code | DĂ©veloppeurs expĂ©rimentĂ©s | +| **[CLAUDE.md](./CLAUDE.md)** | đŸ—ïž Architecture & Build: SDK OpenWrt, structure fichiers, CI/CD | Claude Code, automation | +| **[deploy-module-template.sh](./deploy-module-template.sh)** | 🚀 Script de dĂ©ploiement standardisĂ© avec backup automatique | DevOps | + +**⚠ RĂšgles Critiques:** +1. RPCD naming: fichier = objet ubus (`luci.system-hub`) +2. Menu paths: path menu = fichier vue (`system-hub/overview.js`) +3. Permissions: RPCD=755, CSS/JS=644 +4. **TOUJOURS valider:** `./secubox-tools/validate-modules.sh` + +**Design System (v0.3.0):** InspirĂ© de [demo Cybermind](https://cybermind.fr/apps/system-hub/demo.html) +- Palette dark: `#0a0a0f` (fond), `#6366f1→#8b5cf6` (gradients) +- Fonts: Inter (texte), JetBrains Mono (valeurs) +- CSS classes: `.sh-*` (System Hub), `.sb-*` (SecuBox) + +--- + ## 🎯 Overview SecuBox is a comprehensive security and network management suite for OpenWrt, providing a unified ecosystem of specialized dashboards and tools. All modules are compiled automatically for multiple OpenWrt architectures via GitHub Actions. diff --git a/deploy-module-template.sh b/deploy-module-template.sh new file mode 100755 index 0000000..451c741 --- /dev/null +++ b/deploy-module-template.sh @@ -0,0 +1,268 @@ +#!/bin/bash +# Template de dĂ©ploiement standardisĂ© pour modules SecuBox/System Hub +# Usage: ./deploy-module-template.sh +# +# Exemple: ./deploy-module-template.sh system-hub + +set -e # Exit on error + +# === Configuration === +ROUTER="${ROUTER:-root@192.168.8.191}" +MODULE_NAME="${1}" +BASE_DIR="$(cd "$(dirname "$0")" && pwd)" + +# Chemins +LOCAL_RESOURCES="$BASE_DIR/luci-app-$MODULE_NAME/htdocs/luci-static/resources" +LOCAL_ROOT="$BASE_DIR/luci-app-$MODULE_NAME/root" +REMOTE_RESOURCES="/www/luci-static/resources" +REMOTE_ROOT="" + +# Couleurs pour output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# === Functions === + +print_header() { + echo "" + echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" + echo -e "${BLUE} 📩 DĂ©ploiement: $MODULE_NAME${NC}" + echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" + echo "" +} + +print_step() { + echo -e "${GREEN}▶${NC} $1" +} + +print_error() { + echo -e "${RED}✗${NC} $1" +} + +print_success() { + echo -e "${GREEN}✓${NC} $1" +} + +print_warning() { + echo -e "${YELLOW}⚠${NC} $1" +} + +check_prerequisites() { + print_step "VĂ©rification des prĂ©requis..." + + # VĂ©rifier que le module existe + if [ ! -d "luci-app-$MODULE_NAME" ]; then + print_error "Module luci-app-$MODULE_NAME non trouvĂ©!" + exit 1 + fi + + # VĂ©rifier connectivitĂ© routeur + if ! ssh -o ConnectTimeout=5 "$ROUTER" "exit" 2>/dev/null; then + print_error "Impossible de se connecter Ă  $ROUTER" + exit 1 + fi + + print_success "PrĂ©requis OK" +} + +backup_remote() { + print_step "CrĂ©ation backup sur le routeur..." + + BACKUP_DIR="/root/luci-backups/$(date +%Y%m%d-%H%M%S)" + ssh "$ROUTER" "mkdir -p $BACKUP_DIR" + + # Backup view files + if ssh "$ROUTER" "[ -d $REMOTE_RESOURCES/view/$MODULE_NAME ]" 2>/dev/null; then + ssh "$ROUTER" "cp -r $REMOTE_RESOURCES/view/$MODULE_NAME $BACKUP_DIR/" + fi + + # Backup module files + if ssh "$ROUTER" "[ -d $REMOTE_RESOURCES/$MODULE_NAME ]" 2>/dev/null; then + ssh "$ROUTER" "cp -r $REMOTE_RESOURCES/$MODULE_NAME $BACKUP_DIR/" + fi + + # Backup RPCD + if ssh "$ROUTER" "[ -f /usr/libexec/rpcd/luci.$MODULE_NAME ]" 2>/dev/null; then + ssh "$ROUTER" "cp /usr/libexec/rpcd/luci.$MODULE_NAME $BACKUP_DIR/" + fi + + print_success "Backup créé: $BACKUP_DIR" +} + +deploy_js_files() { + print_step "DĂ©ploiement fichiers JavaScript..." + + if [ -d "$LOCAL_RESOURCES/view/$MODULE_NAME" ]; then + ssh "$ROUTER" "mkdir -p $REMOTE_RESOURCES/view/$MODULE_NAME" + scp -q "$LOCAL_RESOURCES/view/$MODULE_NAME/"*.js "$ROUTER:$REMOTE_RESOURCES/view/$MODULE_NAME/" 2>/dev/null || true + print_success "Fichiers JS vues dĂ©ployĂ©s" + fi + + if [ -f "$LOCAL_RESOURCES/$MODULE_NAME/api.js" ]; then + ssh "$ROUTER" "mkdir -p $REMOTE_RESOURCES/$MODULE_NAME" + scp -q "$LOCAL_RESOURCES/$MODULE_NAME/api.js" "$ROUTER:$REMOTE_RESOURCES/$MODULE_NAME/" 2>/dev/null || true + print_success "API JS dĂ©ployĂ©" + fi +} + +deploy_css_files() { + print_step "DĂ©ploiement fichiers CSS..." + + if [ -d "$LOCAL_RESOURCES/$MODULE_NAME" ]; then + ssh "$ROUTER" "mkdir -p $REMOTE_RESOURCES/$MODULE_NAME" + scp -q "$LOCAL_RESOURCES/$MODULE_NAME/"*.css "$ROUTER:$REMOTE_RESOURCES/$MODULE_NAME/" 2>/dev/null || true + print_success "Fichiers CSS dĂ©ployĂ©s" + fi +} + +deploy_rpcd() { + print_step "DĂ©ploiement backend RPCD..." + + RPCD_FILE="$LOCAL_ROOT/usr/libexec/rpcd/luci.$MODULE_NAME" + if [ -f "$RPCD_FILE" ]; then + scp -q "$RPCD_FILE" "$ROUTER:/usr/libexec/rpcd/" 2>/dev/null || true + print_success "RPCD backend dĂ©ployĂ©" + else + print_warning "Pas de backend RPCD trouvĂ©" + fi +} + +deploy_menu_acl() { + print_step "DĂ©ploiement menu et ACL..." + + # Menu + MENU_FILE="$LOCAL_ROOT/usr/share/luci/menu.d/luci-app-$MODULE_NAME.json" + if [ -f "$MENU_FILE" ]; then + ssh "$ROUTER" "mkdir -p /usr/share/luci/menu.d" + scp -q "$MENU_FILE" "$ROUTER:/usr/share/luci/menu.d/" 2>/dev/null || true + print_success "Menu dĂ©ployĂ©" + fi + + # ACL + ACL_FILE="$LOCAL_ROOT/usr/share/rpcd/acl.d/luci-app-$MODULE_NAME.json" + if [ -f "$ACL_FILE" ]; then + ssh "$ROUTER" "mkdir -p /usr/share/rpcd/acl.d" + scp -q "$ACL_FILE" "$ROUTER:/usr/share/rpcd/acl.d/" 2>/dev/null || true + print_success "ACL dĂ©ployĂ©" + fi +} + +fix_permissions() { + print_step "Correction des permissions..." + + # RPCD = 755 + ssh "$ROUTER" "chmod 755 /usr/libexec/rpcd/luci.$MODULE_NAME 2>/dev/null" || true + + # CSS/JS = 644 + ssh "$ROUTER" "chmod 644 $REMOTE_RESOURCES/$MODULE_NAME/*.css 2>/dev/null" || true + ssh "$ROUTER" "chmod 644 $REMOTE_RESOURCES/$MODULE_NAME/*.js 2>/dev/null" || true + ssh "$ROUTER" "chmod 644 $REMOTE_RESOURCES/view/$MODULE_NAME/*.js 2>/dev/null" || true + + print_success "Permissions corrigĂ©es" +} + +clear_cache() { + print_step "Nettoyage du cache LuCI..." + + ssh "$ROUTER" "rm -f /tmp/luci-indexcache /tmp/luci-modulecache/* 2>/dev/null" || true + + print_success "Cache nettoyĂ©" +} + +restart_services() { + print_step "RedĂ©marrage des services..." + + ssh "$ROUTER" "/etc/init.d/rpcd restart" >/dev/null 2>&1 + sleep 1 + ssh "$ROUTER" "/etc/init.d/uhttpd restart" >/dev/null 2>&1 + sleep 1 + + print_success "Services redĂ©marrĂ©s" +} + +verify_deployment() { + print_step "VĂ©rification du dĂ©ploiement..." + + # VĂ©rifier ubus object + if ssh "$ROUTER" "ubus list | grep -q luci.$MODULE_NAME" 2>/dev/null; then + print_success "ubus object 'luci.$MODULE_NAME' dĂ©tectĂ©" + else + print_warning "ubus object 'luci.$MODULE_NAME' non trouvĂ©" + fi + + # VĂ©rifier fichiers + FILE_COUNT=$(ssh "$ROUTER" "find $REMOTE_RESOURCES -name '*$MODULE_NAME*' -type f | wc -l" 2>/dev/null) + print_success "$FILE_COUNT fichiers dĂ©ployĂ©s" +} + +print_summary() { + echo "" + echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" + echo -e "${GREEN}✓ DĂ©ploiement terminĂ© avec succĂšs!${NC}" + echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" + echo "" + echo -e "${YELLOW}📋 Prochaines Ă©tapes:${NC}" + echo "" + echo "1. Tester en mode privĂ© (Ctrl+Shift+N):" + echo -e " ${BLUE}https://192.168.8.191/cgi-bin/luci/${NC}" + echo "" + echo "2. VĂ©rifier console navigateur (F12):" + echo " - Onglet Console: pas d'erreurs" + echo " - Onglet Network: tous fichiers chargent (200)" + echo "" + echo "3. Tester fonctionnalitĂ©s:" + echo " - Navigation entre pages" + echo " - Chargement des donnĂ©es" + echo " - Actions (boutons, formulaires)" + echo "" + echo "4. Tester responsive:" + echo " - Mode mobile (F12 > Toggle device toolbar)" + echo " - Dark/Light mode" + echo "" + echo -e "${YELLOW}🔧 Debug (si problĂšme):${NC}" + echo "" + echo "# VĂ©rifier ubus" + echo "ssh $ROUTER 'ubus list | grep $MODULE_NAME'" + echo "ssh $ROUTER 'ubus call luci.$MODULE_NAME getStatus'" + echo "" + echo "# VĂ©rifier logs" + echo "ssh $ROUTER 'logread | grep -i error | tail -20'" + echo "" + echo "# Rollback (si nĂ©cessaire)" + echo "ssh $ROUTER 'ls -la /root/luci-backups/'" + echo "" +} + +# === Main Execution === + +main() { + # VĂ©rifier argument + if [ -z "$MODULE_NAME" ]; then + echo "Usage: $0 " + echo "" + echo "Exemples:" + echo " $0 system-hub" + echo " $0 secubox" + echo " $0 netdata-dashboard" + exit 1 + fi + + print_header + check_prerequisites + backup_remote + deploy_js_files + deploy_css_files + deploy_rpcd + deploy_menu_acl + fix_permissions + clear_cache + restart_services + verify_deployment + print_summary +} + +# Run main +main diff --git a/luci-app-secubox/Makefile b/luci-app-secubox/Makefile index a6d7030..8dc214e 100644 --- a/luci-app-secubox/Makefile +++ b/luci-app-secubox/Makefile @@ -1,7 +1,7 @@ include $(TOPDIR)/rules.mk PKG_NAME:=luci-app-secubox -PKG_VERSION:=0.1.2 +PKG_VERSION:=0.2.2 PKG_RELEASE:=1 PKG_LICENSE:=Apache-2.0 PKG_MAINTAINER:=CyberMind diff --git a/luci-app-secubox/htdocs/luci-static/resources/view/secubox/modules.js b/luci-app-secubox/htdocs/luci-static/resources/view/secubox/modules.js index dffc59e..8510650 100644 --- a/luci-app-secubox/htdocs/luci-static/resources/view/secubox/modules.js +++ b/luci-app-secubox/htdocs/luci-static/resources/view/secubox/modules.js @@ -263,7 +263,7 @@ return view.extend({ 'wireguard': 'admin/secubox/network/wireguard', 'network_modes': 'admin/secubox/network/modes', 'client_guardian': 'admin/secubox/security/guardian', - 'system_hub': 'admin/system/system-hub/overview', + 'system_hub': 'admin/secubox/system/system-hub', 'bandwidth_manager': 'admin/secubox/network/bandwidth', 'auth_guardian': 'admin/secubox/security/auth', 'media_flow': 'admin/secubox/network/media', diff --git a/luci-app-secubox/htdocs/luci-static/resources/view/secubox/monitoring.js b/luci-app-secubox/htdocs/luci-static/resources/view/secubox/monitoring.js index 9d6bb11..c0b1b39 100644 --- a/luci-app-secubox/htdocs/luci-static/resources/view/secubox/monitoring.js +++ b/luci-app-secubox/htdocs/luci-static/resources/view/secubox/monitoring.js @@ -6,18 +6,6 @@ 'require secubox/theme as Theme'; 'require poll'; -// Load CSS (base theme variables first) -document.head.appendChild(E('link', { - 'rel': 'stylesheet', - 'type': 'text/css', - 'href': L.resource('secubox/secubox.css') -})); -document.head.appendChild(E('link', { - 'rel': 'stylesheet', - 'type': 'text/css', - 'href': L.resource('secubox/monitoring.css') -})); - // Initialize theme Theme.init(); @@ -87,7 +75,11 @@ return view.extend({ render: function(data) { var self = this; - var container = E('div', { 'class': 'secubox-monitoring-page' }); + var container = E('div', { 'class': 'secubox-monitoring-page' }, [ + E('link', { 'rel': 'stylesheet', 'href': L.resource('system-hub/common.css') }), + E('link', { 'rel': 'stylesheet', 'href': L.resource('secubox/secubox.css') }), + E('link', { 'rel': 'stylesheet', 'href': L.resource('secubox/monitoring.css') }) + ]); // Header container.appendChild(this.renderHeader()); @@ -117,21 +109,49 @@ return view.extend({ }, renderHeader: function() { - return E('div', { 'class': 'secubox-page-header secubox-monitoring-header' }, [ + var latest = { + cpu: this.cpuHistory[this.cpuHistory.length - 1] || { value: 0 }, + memory: this.memoryHistory[this.memoryHistory.length - 1] || { value: 0 }, + disk: this.diskHistory[this.diskHistory.length - 1] || { value: 0 } + }; + + return E('div', { 'class': 'sh-page-header' }, [ E('div', {}, [ - E('h2', {}, '📊 System Monitoring'), - E('p', { 'class': 'secubox-page-subtitle' }, + E('h2', { 'class': 'sh-page-title' }, [ + E('span', { 'class': 'sh-page-title-icon' }, '📊'), + 'System Monitoring' + ]), + E('p', { 'class': 'sh-page-subtitle' }, 'Real-time system performance metrics and historical trends') ]), - E('div', { 'class': 'secubox-header-info' }, [ - E('span', { 'class': 'secubox-badge' }, - '⏱ Refresh: Every 5 seconds'), - E('span', { 'class': 'secubox-badge' }, - '📈 History: Last ' + this.maxDataPoints + ' points') + E('div', { 'class': 'sh-stats-grid' }, [ + E('div', { 'class': 'sh-stat-badge' }, [ + E('div', { 'class': 'sh-stat-value', 'style': 'color: ' + this.getColorForValue(latest.cpu.value) }, latest.cpu.value.toFixed(1) + '%'), + E('div', { 'class': 'sh-stat-label' }, 'CPU') + ]), + E('div', { 'class': 'sh-stat-badge' }, [ + E('div', { 'class': 'sh-stat-value', 'style': 'color: ' + this.getColorForValue(latest.memory.value) }, latest.memory.value.toFixed(1) + '%'), + E('div', { 'class': 'sh-stat-label' }, 'Memory') + ]), + E('div', { 'class': 'sh-stat-badge' }, [ + E('div', { 'class': 'sh-stat-value', 'style': 'color: ' + this.getColorForValue(latest.disk.value) }, latest.disk.value.toFixed(1) + '%'), + E('div', { 'class': 'sh-stat-label' }, 'Disk') + ]), + E('div', { 'class': 'sh-stat-badge' }, [ + E('div', { 'class': 'sh-stat-value' }, this.cpuHistory.length), + E('div', { 'class': 'sh-stat-label' }, 'Data Points') + ]) ]) ]); }, + getColorForValue: function(value) { + if (value >= 90) return '#ef4444'; + if (value >= 75) return '#f59e0b'; + if (value >= 50) return '#3b82f6'; + return '#22c55e'; + }, + renderChart: function(type, title, unit) { return E('div', { 'class': 'secubox-chart-card' }, [ E('h3', { 'class': 'secubox-chart-title' }, title), @@ -353,6 +373,24 @@ return view.extend({ if (container) { dom.content(container, this.renderStatsTable()); } + + // Update header stats + var latest = { + cpu: this.cpuHistory[this.cpuHistory.length - 1] || { value: 0 }, + memory: this.memoryHistory[this.memoryHistory.length - 1] || { value: 0 }, + disk: this.diskHistory[this.diskHistory.length - 1] || { value: 0 } + }; + + var statBadges = document.querySelectorAll('.sh-stat-value'); + if (statBadges.length >= 4) { + statBadges[0].textContent = latest.cpu.value.toFixed(1) + '%'; + statBadges[0].style.color = this.getColorForValue(latest.cpu.value); + statBadges[1].textContent = latest.memory.value.toFixed(1) + '%'; + statBadges[1].style.color = this.getColorForValue(latest.memory.value); + statBadges[2].textContent = latest.disk.value.toFixed(1) + '%'; + statBadges[2].style.color = this.getColorForValue(latest.disk.value); + statBadges[3].textContent = this.cpuHistory.length; + } }, handleSaveApply: null, diff --git a/luci-app-secubox/htdocs/luci-static/resources/view/secubox/settings.js b/luci-app-secubox/htdocs/luci-static/resources/view/secubox/settings.js index 4fce7a8..de93fd2 100644 --- a/luci-app-secubox/htdocs/luci-static/resources/view/secubox/settings.js +++ b/luci-app-secubox/htdocs/luci-static/resources/view/secubox/settings.js @@ -6,23 +6,55 @@ 'require secubox/api as API'; 'require secubox/theme as Theme'; -// Initialize theme -Theme.init(); - return view.extend({ load: function() { return Promise.all([ uci.load('secubox'), - API.getStatus() + API.getStatus(), + Theme.getTheme() ]); }, render: function(data) { var status = data[1] || {}; + var theme = data[2]; var m, s, o; - m = new form.Map('secubox', '⚙ SecuBox Settings', - 'Configure global settings for the SecuBox security suite.'); + // Create wrapper container with modern header + var container = E('div', { 'class': 'secubox-settings-page' }, [ + E('link', { 'rel': 'stylesheet', 'href': L.resource('system-hub/common.css') }), + E('link', { 'rel': 'stylesheet', 'href': L.resource('secubox/secubox.css') }), + + // Modern header + E('div', { 'class': 'sh-page-header' }, [ + E('div', {}, [ + E('h2', { 'class': 'sh-page-title' }, [ + E('span', { 'class': 'sh-page-title-icon' }, '⚙'), + 'SecuBox Settings' + ]), + E('p', { 'class': 'sh-page-subtitle' }, + 'Configure global settings for the SecuBox security suite') + ]), + E('div', { 'class': 'sh-stats-grid' }, [ + E('div', { 'class': 'sh-stat-badge' }, [ + E('div', { 'class': 'sh-stat-value' }, status.version || 'v0.1.2'), + E('div', { 'class': 'sh-stat-label' }, 'Version') + ]), + E('div', { 'class': 'sh-stat-badge' }, [ + E('div', { 'class': 'sh-stat-value', 'style': status.enabled ? 'color: #22c55e;' : 'color: #ef4444;' }, + status.enabled ? 'ON' : 'OFF'), + E('div', { 'class': 'sh-stat-label' }, 'Status') + ]), + E('div', { 'class': 'sh-stat-badge' }, [ + E('div', { 'class': 'sh-stat-value' }, status.modules_count || '14'), + E('div', { 'class': 'sh-stat-label' }, 'Modules') + ]) + ]) + ]) + ]); + + // Create form + m = new form.Map('secubox', null, null); // General Settings Section s = m.section(form.TypedSection, 'secubox', '🔧 General Settings'); @@ -37,7 +69,7 @@ return view.extend({ o = s.option(form.Value, 'version', '📩 Version', 'Current SecuBox version (read-only)'); o.readonly = true; - o.default = '0.1.0'; + o.default = '0.1.2'; // Dashboard Settings Section s = m.section(form.TypedSection, 'secubox', '📊 Dashboard Settings'); @@ -208,6 +240,10 @@ return view.extend({ o.default = '20'; o.placeholder = '20'; - return m.render(); + // Render form and append to container + return m.render().then(L.bind(function(formElement) { + container.appendChild(formElement); + return container; + }, this)); } }); diff --git a/luci-app-system-hub/Makefile b/luci-app-system-hub/Makefile index a83e860..617afa8 100644 --- a/luci-app-system-hub/Makefile +++ b/luci-app-system-hub/Makefile @@ -1,7 +1,7 @@ include $(TOPDIR)/rules.mk PKG_NAME:=luci-app-system-hub -PKG_VERSION:=0.1.1 +PKG_VERSION:=0.2.2 PKG_RELEASE:=1 PKG_LICENSE:=Apache-2.0 PKG_MAINTAINER:=CyberMind diff --git a/luci-app-system-hub/htdocs/luci-static/resources/system-hub/api.js b/luci-app-system-hub/htdocs/luci-static/resources/system-hub/api.js index 82d80bb..99aa2ab 100644 --- a/luci-app-system-hub/htdocs/luci-static/resources/system-hub/api.js +++ b/luci-app-system-hub/htdocs/luci-static/resources/system-hub/api.js @@ -6,11 +6,11 @@ * System Hub API * Package: luci-app-system-hub * RPCD object: luci.system-hub - * Version: 0.1.1 + * Version: 0.2.2 */ // Debug log to verify correct version is loaded -console.log('🔧 System Hub API v0.1.1 loaded at', new Date().toISOString()); +console.log('🔧 System Hub API v0.2.2 loaded at', new Date().toISOString()); var callStatus = rpc.declare({ object: 'luci.system-hub', diff --git a/luci-app-system-hub/htdocs/luci-static/resources/system-hub/common.css b/luci-app-system-hub/htdocs/luci-static/resources/system-hub/common.css new file mode 100644 index 0000000..233598e --- /dev/null +++ b/luci-app-system-hub/htdocs/luci-static/resources/system-hub/common.css @@ -0,0 +1,549 @@ +/** + * System Hub - Common Styles (Demo-inspired) + * Shared styles across all System Hub pages + * Version: 0.3.0 - Matching https://cybermind.fr/apps/system-hub/demo.html + */ + +/* === Import Fonts === */ +@import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600;700&family=Inter:wght@400;500;600;700&display=swap'); + +/* === Variables (Demo-inspired Dark Mode) === */ +:root { + /* Light Mode (less used) */ + --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; + --sh-hover-bg: #f8fafc; + --sh-hover-shadow: rgba(0, 0, 0, 0.1); + --sh-primary: #6366f1; + --sh-primary-end: #8b5cf6; + --sh-shadow: rgba(0, 0, 0, 0.08); + --sh-success: #22c55e; + --sh-danger: #ef4444; + --sh-warning: #f59e0b; +} + +[data-theme="dark"] { + /* Demo-inspired Dark Palette */ + --sh-text-primary: #fafafa; + --sh-text-secondary: #a0a0b0; + --sh-bg-primary: #0a0a0f; + --sh-bg-secondary: #12121a; + --sh-bg-tertiary: #1a1a24; + --sh-bg-card: #12121a; + --sh-border: #2a2a35; + --sh-hover-bg: #1a1a24; + --sh-hover-shadow: rgba(0, 0, 0, 0.6); + --sh-primary: #6366f1; + --sh-primary-end: #8b5cf6; + --sh-shadow: rgba(0, 0, 0, 0.4); + --sh-success: #22c55e; + --sh-danger: #ef4444; + --sh-warning: #f59e0b; +} + +/* === Global Typography === */ +body, +.system-hub-dashboard, +.sh-page-header, +.sh-card { + font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif; +} + +code, +.sh-mono, +.sh-id-display, +pre { + font-family: 'JetBrains Mono', 'Courier New', monospace; +} + +/* === Page Header === */ +.sh-page-header { + margin-bottom: 24px; + padding: 24px; + background: var(--sh-bg-card); + border-radius: 16px; + border: 1px solid var(--sh-border); + display: flex; + justify-content: space-between; + align-items: center; + flex-wrap: wrap; + gap: 20px; +} + +.sh-page-title { + font-size: 28px; + font-weight: 700; + margin: 0; + background: linear-gradient(135deg, var(--sh-primary), var(--sh-primary-end)); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; + display: flex; + align-items: center; + gap: 12px; +} + +.sh-page-title-icon { + font-size: 32px; + line-height: 1; + -webkit-text-fill-color: initial; +} + +.sh-page-subtitle { + margin: 4px 0 0 0; + font-size: 14px; + color: var(--sh-text-secondary); + font-weight: 500; +} + +/* === Stats Badges (Compact Demo Style) === */ +.sh-stats-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(130px, 1fr)); + gap: 12px; + margin: 20px 0; +} + +.sh-stat-badge { + display: flex; + flex-direction: column; + align-items: center; + padding: 16px 12px; + background: var(--sh-bg-card); + border: 1px solid var(--sh-border); + border-radius: 12px; + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + position: relative; + overflow: hidden; +} + +.sh-stat-badge::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + height: 3px; + background: linear-gradient(90deg, var(--sh-primary), var(--sh-primary-end)); + opacity: 0; + transition: opacity 0.3s ease; +} + +.sh-stat-badge:hover { + transform: translateY(-3px); + box-shadow: 0 8px 20px var(--sh-shadow); + border-color: var(--sh-primary); +} + +.sh-stat-badge:hover::before { + opacity: 1; +} + +.sh-stat-value { + font-size: 28px; + font-weight: 700; + line-height: 1; + margin-bottom: 6px; + color: var(--sh-text-primary); + font-family: 'JetBrains Mono', monospace; +} + +.sh-stat-label { + font-size: 11px; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.8px; + color: var(--sh-text-secondary); +} + +/* === Navigation Tabs (Internal Demo Style) === */ +.sh-nav-tabs { + display: flex; + gap: 8px; + background: var(--sh-bg-secondary); + padding: 8px; + border-radius: 12px; + border: 1px solid var(--sh-border); + margin-bottom: 24px; + overflow-x: auto; + position: sticky; + top: 0; + z-index: 100; + backdrop-filter: blur(10px); +} + +.sh-nav-tab { + display: flex; + align-items: center; + gap: 8px; + padding: 10px 18px; + background: transparent; + border: none; + border-radius: 8px; + cursor: pointer; + transition: all 0.2s ease; + font-size: 14px; + font-weight: 600; + color: var(--sh-text-secondary); + white-space: nowrap; + position: relative; +} + +.sh-nav-tab:hover { + background: var(--sh-hover-bg); + color: var(--sh-text-primary); +} + +.sh-nav-tab.active { + color: var(--sh-primary); + background: rgba(99, 102, 241, 0.1); +} + +.sh-nav-tab.active::after { + content: ''; + position: absolute; + bottom: 0; + left: 10%; + right: 10%; + height: 2px; + background: linear-gradient(90deg, var(--sh-primary), var(--sh-primary-end)); + border-radius: 2px; +} + +/* === Filter Tabs === */ +.sh-filter-tabs { + display: flex; + gap: 12px; + flex-wrap: wrap; + background: var(--sh-bg-secondary); + padding: 12px; + border-radius: 12px; + border: 1px solid var(--sh-border); + margin-bottom: 24px; +} + +.sh-filter-tab { + display: flex; + align-items: center; + gap: 8px; + padding: 10px 20px; + background: var(--sh-bg-card); + border: 2px solid transparent; + border-radius: 8px; + cursor: pointer; + transition: all 0.2s ease; + font-size: 14px; + font-weight: 600; + color: var(--sh-text-secondary); + user-select: none; +} + +.sh-filter-tab:hover { + background: var(--sh-hover-bg); + border-color: var(--sh-primary); + transform: translateY(-2px); +} + +.sh-filter-tab.active { + background: linear-gradient(135deg, var(--sh-primary), var(--sh-primary-end)); + color: #ffffff; + border-color: transparent; + box-shadow: 0 4px 12px rgba(99, 102, 241, 0.4); +} + +.sh-tab-icon { + font-size: 18px; + line-height: 1; +} + +.sh-tab-label { + white-space: nowrap; +} + +/* === Cards (with colored top border) === */ +.sh-card { + background: var(--sh-bg-card); + border-radius: 16px; + border: 1px solid var(--sh-border); + overflow: hidden; + transition: all 0.3s ease; + margin-bottom: 20px; + position: relative; +} + +.sh-card::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + height: 3px; + background: linear-gradient(90deg, var(--sh-primary), var(--sh-primary-end)); + opacity: 0; + transition: opacity 0.3s ease; +} + +.sh-card:hover { + transform: translateY(-3px); + box-shadow: 0 12px 28px var(--sh-hover-shadow); + border-color: var(--sh-border); +} + +.sh-card:hover::before { + opacity: 1; +} + +/* Colored borders for different card types */ +.sh-card-success::before { + background: var(--sh-success); + opacity: 1; +} + +.sh-card-danger::before { + background: var(--sh-danger); + opacity: 1; +} + +.sh-card-warning::before { + background: var(--sh-warning); + opacity: 1; +} + +.sh-card-header { + padding: 20px 24px; + background: var(--sh-bg-secondary); + border-bottom: 1px solid var(--sh-border); + display: flex; + justify-content: space-between; + align-items: center; +} + +.sh-card-title { + font-size: 18px; + font-weight: 700; + color: var(--sh-text-primary); + display: flex; + align-items: center; + gap: 10px; + margin: 0; +} + +.sh-card-title-icon { + font-size: 22px; + line-height: 1; +} + +.sh-card-badge { + padding: 6px 14px; + background: linear-gradient(135deg, var(--sh-primary), var(--sh-primary-end)); + color: #ffffff; + border-radius: 20px; + font-size: 12px; + font-weight: 700; +} + +.sh-card-body { + padding: 24px; +} + +/* === Empty State === */ +.sh-empty-state { + text-align: center; + padding: 60px 20px; + background: var(--sh-bg-secondary); + border-radius: 12px; + border: 2px dashed var(--sh-border); +} + +.sh-empty-icon { + font-size: 64px; + margin-bottom: 16px; + opacity: 0.5; +} + +.sh-empty-text { + font-size: 16px; + color: var(--sh-text-secondary); + font-weight: 600; +} + +/* === Buttons (Gradient Style) === */ +.sh-btn { + display: inline-flex; + align-items: center; + justify-content: center; + gap: 6px; + padding: 10px 20px; + border-radius: 8px; + font-size: 14px; + font-weight: 600; + text-decoration: none; + border: none; + cursor: pointer; + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + position: relative; + overflow: hidden; +} + +.sh-btn-primary { + background: linear-gradient(135deg, var(--sh-primary), var(--sh-primary-end)); + color: #ffffff; + box-shadow: 0 4px 12px rgba(99, 102, 241, 0.3); +} + +.sh-btn-primary:hover { + transform: translateY(-2px); + box-shadow: 0 8px 20px rgba(99, 102, 241, 0.5); +} + +.sh-btn-success { + background: var(--sh-success); + color: #ffffff; + box-shadow: 0 4px 12px rgba(34, 197, 94, 0.3); +} + +.sh-btn-success:hover { + background: #16a34a; + transform: translateY(-2px); + box-shadow: 0 8px 20px rgba(34, 197, 94, 0.5); +} + +.sh-btn-danger { + background: var(--sh-danger); + color: #ffffff; + box-shadow: 0 4px 12px rgba(239, 68, 68, 0.3); +} + +.sh-btn-danger:hover { + background: #dc2626; + transform: translateY(-2px); + box-shadow: 0 8px 20px rgba(239, 68, 68, 0.5); +} + +.sh-btn-warning { + background: var(--sh-warning); + color: #ffffff; + box-shadow: 0 4px 12px rgba(245, 158, 11, 0.3); +} + +.sh-btn-warning:hover { + background: #d97706; + transform: translateY(-2px); + box-shadow: 0 8px 20px rgba(245, 158, 11, 0.5); +} + +.sh-btn-secondary { + background: var(--sh-bg-tertiary); + color: var(--sh-text-primary); + border: 1px solid var(--sh-border); +} + +.sh-btn-secondary:hover { + background: var(--sh-hover-bg); + border-color: var(--sh-primary); + transform: translateY(-2px); +} + +/* === ID Display (Monospace) === */ +.sh-id-display { + font-size: 24px; + font-weight: 700; + font-family: 'JetBrains Mono', monospace; + color: var(--sh-text-primary); + background: var(--sh-bg-secondary); + padding: 12px 20px; + border-radius: 8px; + border: 1px solid var(--sh-border); + text-align: center; + letter-spacing: 2px; +} + +/* === Gradient Text Utility === */ +.sh-gradient-text { + background: linear-gradient(135deg, var(--sh-primary), var(--sh-primary-end)); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; +} + +/* === Responsive === */ +@media (max-width: 768px) { + .sh-page-title { + font-size: 22px; + } + + .sh-filter-tabs { + gap: 8px; + } + + .sh-filter-tab { + padding: 8px 16px; + font-size: 13px; + } + + .sh-stats-grid { + grid-template-columns: repeat(2, 1fr); + } + + .sh-nav-tabs { + gap: 4px; + padding: 6px; + } + + .sh-nav-tab { + padding: 8px 14px; + font-size: 13px; + } +} + +/* === Dark Mode Overrides === */ +[data-theme="dark"] .sh-card, +[data-theme="dark"] .sh-stat-badge, +[data-theme="dark"] .sh-filter-tabs, +[data-theme="dark"] .sh-nav-tabs, +[data-theme="dark"] .sh-page-header { + background: var(--sh-bg-card); + border-color: var(--sh-border); +} + +[data-theme="dark"] .sh-card-header, +[data-theme="dark"] .sh-filter-tabs, +[data-theme="dark"] .sh-nav-tabs { + background: var(--sh-bg-secondary); + border-color: var(--sh-border); +} + +[data-theme="dark"] .sh-filter-tab, +[data-theme="dark"] .sh-nav-tab { + background: transparent; +} + +[data-theme="dark"] .sh-filter-tab:hover, +[data-theme="dark"] .sh-nav-tab:hover { + background: var(--sh-hover-bg); +} + +[data-theme="dark"] .sh-btn-secondary { + background: var(--sh-bg-tertiary); + border-color: var(--sh-border); + color: var(--sh-text-primary); +} + +[data-theme="dark"] .sh-btn-secondary:hover { + background: var(--sh-hover-bg); +} + +[data-theme="dark"] .sh-empty-state { + background: var(--sh-bg-secondary); + border-color: var(--sh-border); +} + +[data-theme="dark"] .sh-id-display { + background: var(--sh-bg-secondary); + border-color: var(--sh-border); +} diff --git a/luci-app-system-hub/htdocs/luci-static/resources/system-hub/components.css b/luci-app-system-hub/htdocs/luci-static/resources/system-hub/components.css index aa31339..6d9426f 100644 --- a/luci-app-system-hub/htdocs/luci-static/resources/system-hub/components.css +++ b/luci-app-system-hub/htdocs/luci-static/resources/system-hub/components.css @@ -1,7 +1,7 @@ /** * System Hub - Components Page Styles * Responsive card layout with theme support - * Version: 0.1.2 + * Version: 0.2.2 */ /* === Header & Filters === */ diff --git a/luci-app-system-hub/htdocs/luci-static/resources/system-hub/overview.css b/luci-app-system-hub/htdocs/luci-static/resources/system-hub/overview.css index 9caf1e6..3de3a9d 100644 --- a/luci-app-system-hub/htdocs/luci-static/resources/system-hub/overview.css +++ b/luci-app-system-hub/htdocs/luci-static/resources/system-hub/overview.css @@ -1,52 +1,186 @@ /** - * System Hub - Overview Page Styles + * System Hub - Overview Page Styles (Demo-inspired) * Modern dashboard with widgets and metrics - * Version: 0.2.0 + * Version: 0.3.0 - Matching https://cybermind.fr/apps/system-hub/demo.html */ -/* === Header === */ +/* === Dashboard Header (Demo Style - more subtle) === */ +.sh-dashboard-header { + background: linear-gradient(135deg, rgba(99, 102, 241, 0.15) 0%, rgba(139, 92, 246, 0.15) 100%); + border-radius: 16px; + padding: 28px; + margin-bottom: 24px; + border: 1px solid var(--sh-border); + color: var(--sh-text-primary); +} + +.sh-dashboard-header-content { + display: flex; + justify-content: space-between; + align-items: center; + flex-wrap: wrap; + gap: 16px; +} + +.sh-dashboard-header h2 { + margin: 0 0 6px 0; + font-size: 28px; + font-weight: 700; + letter-spacing: -0.5px; + background: linear-gradient(135deg, var(--sh-primary), var(--sh-primary-end)); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; +} + +.sh-dashboard-subtitle { + margin: 0; + opacity: 0.9; + font-size: 14px; + font-weight: 500; + color: var(--sh-text-secondary); +} + +.sh-dashboard-header-info { + display: flex; + gap: 10px; + flex-wrap: wrap; +} + +.sh-dashboard-badge { + background: var(--sh-bg-card); + padding: 8px 16px; + border-radius: 20px; + font-size: 13px; + font-weight: 600; + border: 1px solid var(--sh-border); + white-space: nowrap; + color: var(--sh-text-primary); +} + +.sh-dashboard-badge-version { + background: linear-gradient(135deg, var(--sh-primary), var(--sh-primary-end)); + color: #ffffff; + border-color: transparent; + font-weight: 700; +} + +/* === Stats Overview Grid (Demo Style - Compact) === */ +.sh-stats-overview-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(130px, 1fr)); + gap: 16px; + margin-bottom: 32px; +} + +.sh-stat-overview-card { + background: var(--sh-bg-card); + border-radius: 12px; + padding: 20px 16px; + text-align: center; + border: 1px solid var(--sh-border); + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + position: relative; + overflow: hidden; +} + +.sh-stat-overview-card::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + height: 3px; + transition: opacity 0.3s ease; +} + +.sh-stat-overview-card:hover { + transform: translateY(-3px); + box-shadow: 0 12px 28px var(--sh-shadow); + border-color: var(--sh-primary); +} + +.sh-stat-excellent::before { background: #22c55e; opacity: 1; } +.sh-stat-good::before { background: #3b82f6; opacity: 1; } +.sh-stat-warning::before { background: #f59e0b; opacity: 1; } +.sh-stat-critical::before { background: #ef4444; opacity: 1; } +.sh-stat-cpu::before { background: linear-gradient(90deg, #6366f1, #8b5cf6); opacity: 1; } +.sh-stat-memory::before { background: linear-gradient(90deg, #8b5cf6, #ec4899); opacity: 1; } +.sh-stat-disk::before { background: linear-gradient(90deg, #ec4899, #f43f5e); opacity: 1; } + +.sh-stat-overview-icon { + font-size: 36px; + margin-bottom: 10px; + line-height: 1; +} + +.sh-stat-overview-value { + font-size: 32px; + font-weight: 700; + line-height: 1; + margin-bottom: 10px; + color: var(--sh-text-primary); + font-family: 'JetBrains Mono', monospace; +} + +.sh-stat-overview-label { + font-size: 11px; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.8px; + color: var(--sh-text-secondary); + margin-bottom: 4px; +} + +.sh-stat-overview-status { + font-size: 12px; + font-weight: 600; + color: var(--sh-text-secondary); +} + +/* === Old Header (deprecated, keep for compatibility) === */ .sh-overview-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 32px; padding: 24px; - background: var(--sh-bg-card, #ffffff); + background: var(--sh-bg-card); border-radius: 16px; - border: 1px solid var(--sh-border, #e2e8f0); + border: 1px solid var(--sh-border); } .sh-overview-title h2 { - font-size: 32px; + font-size: 28px; font-weight: 700; margin: 0 0 8px 0; - color: var(--sh-text-primary, #1e293b); + color: var(--sh-text-primary); display: flex; align-items: center; gap: 12px; } .sh-title-icon { - font-size: 36px; + font-size: 32px; } .sh-overview-subtitle { margin: 0; - font-size: 16px; - color: var(--sh-text-secondary, #64748b); + font-size: 14px; + color: var(--sh-text-secondary); font-weight: 500; } /* === Health Score Circle === */ .sh-score-circle { - width: 120px; - height: 120px; + width: 100px; + height: 100px; border-radius: 50%; display: flex; flex-direction: column; align-items: center; justify-content: center; - border: 6px solid; + border: 5px solid; transition: all 0.3s ease; } @@ -71,34 +205,35 @@ } .sh-score-value { - font-size: 42px; + font-size: 36px; font-weight: 700; line-height: 1; - color: var(--sh-text-primary, #1e293b); + color: var(--sh-text-primary); + font-family: 'JetBrains Mono', monospace; } .sh-score-label { - font-size: 12px; + font-size: 10px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px; - color: var(--sh-text-secondary, #64748b); + color: var(--sh-text-secondary); margin-top: 4px; } /* === Metrics Grid === */ .sh-metrics-grid { display: grid; - grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); + grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 20px; margin-bottom: 32px; } .sh-metric-card { - background: var(--sh-bg-card, #ffffff); + background: var(--sh-bg-card); border-radius: 16px; padding: 24px; - border: 1px solid var(--sh-border, #e2e8f0); + border: 1px solid var(--sh-border); transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); position: relative; overflow: hidden; @@ -110,15 +245,15 @@ top: 0; left: 0; right: 0; - height: 4px; - background: linear-gradient(90deg, #6366f1, #8b5cf6); + height: 3px; + background: linear-gradient(90deg, var(--sh-primary), var(--sh-primary-end)); opacity: 0; transition: opacity 0.3s ease; } .sh-metric-card:hover { - transform: translateY(-4px); - box-shadow: 0 12px 28px var(--sh-hover-shadow, rgba(0, 0, 0, 0.12)); + transform: translateY(-3px); + box-shadow: 0 12px 28px var(--sh-hover-shadow); } .sh-metric-card:hover::before { @@ -133,74 +268,93 @@ } .sh-metric-icon { - font-size: 32px; + font-size: 28px; line-height: 1; } .sh-metric-title { - font-size: 16px; + font-size: 15px; font-weight: 600; - color: var(--sh-text-primary, #1e293b); + color: var(--sh-text-primary); } .sh-metric-value { - font-size: 48px; + font-size: 40px; font-weight: 700; line-height: 1; margin-bottom: 16px; - color: var(--sh-text-primary, #1e293b); + color: var(--sh-text-primary); + font-family: 'JetBrains Mono', monospace; } .sh-metric-progress { - height: 8px; - background: var(--sh-bg-tertiary, #f1f5f9); - border-radius: 4px; + height: 6px; + background: var(--sh-bg-tertiary); + border-radius: 3px; overflow: hidden; margin-bottom: 12px; } .sh-metric-progress-bar { height: 100%; - border-radius: 4px; + border-radius: 3px; transition: width 0.5s ease; + background: linear-gradient(90deg, var(--sh-primary), var(--sh-primary-end)); } .sh-metric-details { - font-size: 14px; - color: var(--sh-text-secondary, #64748b); + font-size: 13px; + color: var(--sh-text-secondary); font-weight: 500; } /* === Info Grid === */ .sh-info-grid { display: grid; - grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); + grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 20px; } .sh-info-card { - background: var(--sh-bg-card, #ffffff); + background: var(--sh-bg-card); border-radius: 16px; - border: 1px solid var(--sh-border, #e2e8f0); + border: 1px solid var(--sh-border); overflow: hidden; transition: all 0.3s ease; + position: relative; +} + +.sh-info-card::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + height: 3px; + background: linear-gradient(90deg, var(--sh-primary), var(--sh-primary-end)); + opacity: 0; + transition: opacity 0.3s ease; } .sh-info-card:hover { - box-shadow: 0 4px 12px var(--sh-hover-shadow, rgba(0, 0, 0, 0.08)); + box-shadow: 0 8px 20px var(--sh-hover-shadow); +} + +.sh-info-card:hover::before { + opacity: 1; } .sh-info-card-header { - padding: 20px 24px; - background: var(--sh-bg-secondary, #f8fafc); - border-bottom: 1px solid var(--sh-border, #e2e8f0); + padding: 18px 24px; + background: var(--sh-bg-secondary); + border-bottom: 1px solid var(--sh-border); } .sh-info-card-header h3 { margin: 0; - font-size: 18px; + font-size: 16px; font-weight: 700; - color: var(--sh-text-primary, #1e293b); + color: var(--sh-text-primary); } .sh-info-card-body { @@ -215,7 +369,7 @@ .sh-info-row { display: grid; - grid-template-columns: 40px 1fr auto; + grid-template-columns: 36px 1fr auto; align-items: center; gap: 12px; padding: 12px 16px; @@ -224,24 +378,24 @@ } .sh-info-row:hover { - background: var(--sh-hover-bg, #f8fafc); + background: var(--sh-hover-bg); } .sh-info-icon { - font-size: 20px; + font-size: 18px; text-align: center; } .sh-info-label { - font-size: 14px; + font-size: 13px; font-weight: 600; - color: var(--sh-text-secondary, #64748b); + color: var(--sh-text-secondary); } .sh-info-value { - font-size: 14px; + font-size: 13px; font-weight: 600; - color: var(--sh-text-primary, #1e293b); + color: var(--sh-text-primary); text-align: right; } @@ -250,24 +404,24 @@ display: inline-block; padding: 4px 12px; border-radius: 12px; - font-size: 13px; + font-size: 12px; font-weight: 600; line-height: 1.4; } .sh-status-ok { background: rgba(34, 197, 94, 0.15); - color: #16a34a; + color: #22c55e; } .sh-status-error { background: rgba(239, 68, 68, 0.15); - color: #dc2626; + color: #ef4444; } .sh-status-warning { background: rgba(245, 158, 11, 0.15); - color: #d97706; + color: #f59e0b; } /* === Link Button === */ @@ -276,18 +430,19 @@ align-items: center; gap: 6px; padding: 8px 16px; - background: var(--sh-primary, #6366f1); + background: linear-gradient(135deg, var(--sh-primary), var(--sh-primary-end)); color: #ffffff; border-radius: 8px; - font-size: 14px; + font-size: 13px; font-weight: 600; text-decoration: none; - transition: all 0.2s ease; + transition: all 0.3s ease; + box-shadow: 0 4px 12px rgba(99, 102, 241, 0.3); } .sh-link-button:hover { - background: #4f46e5; - transform: translateX(4px); + transform: translateY(-2px); + box-shadow: 0 8px 20px rgba(99, 102, 241, 0.5); text-decoration: none; } @@ -312,32 +467,35 @@ } .sh-metric-value { - font-size: 36px; + font-size: 32px; + } + + .sh-stats-overview-grid { + grid-template-columns: repeat(2, 1fr); + } + + .sh-dashboard-header h2 { + font-size: 22px; } } /* === Dark Mode === */ -[data-theme="dark"] { - --sh-text-primary: #f1f5f9; - --sh-text-secondary: #cbd5e1; - --sh-bg-primary: #0f172a; - --sh-bg-secondary: #1e293b; - --sh-bg-tertiary: #334155; - --sh-bg-card: #1e293b; - --sh-border: #334155; - --sh-hover-bg: #334155; - --sh-hover-shadow: rgba(0, 0, 0, 0.4); +[data-theme="dark"] .sh-dashboard-header { + background: linear-gradient(135deg, rgba(99, 102, 241, 0.1) 0%, rgba(139, 92, 246, 0.1) 100%); + border-color: var(--sh-border); } [data-theme="dark"] .sh-metric-card, [data-theme="dark"] .sh-info-card, -[data-theme="dark"] .sh-overview-header { +[data-theme="dark"] .sh-overview-header, +[data-theme="dark"] .sh-stat-overview-card, +[data-theme="dark"] .sh-dashboard-badge { background: var(--sh-bg-card); border-color: var(--sh-border); } [data-theme="dark"] .sh-info-card-header { - background: var(--sh-bg-tertiary); + background: var(--sh-bg-secondary); border-color: var(--sh-border); } @@ -350,17 +508,17 @@ } [data-theme="dark"] .sh-score-excellent { - background: linear-gradient(135deg, rgba(34, 197, 94, 0.2), rgba(34, 197, 94, 0.1)); + background: linear-gradient(135deg, rgba(34, 197, 94, 0.15), rgba(34, 197, 94, 0.08)); } [data-theme="dark"] .sh-score-good { - background: linear-gradient(135deg, rgba(59, 130, 246, 0.2), rgba(59, 130, 246, 0.1)); + background: linear-gradient(135deg, rgba(59, 130, 246, 0.15), rgba(59, 130, 246, 0.08)); } [data-theme="dark"] .sh-score-warning { - background: linear-gradient(135deg, rgba(245, 158, 11, 0.2), rgba(245, 158, 11, 0.1)); + background: linear-gradient(135deg, rgba(245, 158, 11, 0.15), rgba(245, 158, 11, 0.08)); } [data-theme="dark"] .sh-score-critical { - background: linear-gradient(135deg, rgba(239, 68, 68, 0.2), rgba(239, 68, 68, 0.1)); + background: linear-gradient(135deg, rgba(239, 68, 68, 0.15), rgba(239, 68, 68, 0.08)); } diff --git a/luci-app-system-hub/htdocs/luci-static/resources/system-hub/services.css b/luci-app-system-hub/htdocs/luci-static/resources/system-hub/services.css new file mode 100644 index 0000000..4b49563 --- /dev/null +++ b/luci-app-system-hub/htdocs/luci-static/resources/system-hub/services.css @@ -0,0 +1,89 @@ +/** + * System Hub - Services Page Styles + * Modern card-based service management interface + * Version: 0.2.2 + */ + +/* === Services Grid === */ +.sh-services-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); + gap: 20px; + margin-bottom: 24px; +} + +/* === Service Cards === */ +.sh-services-grid .sh-card { + margin-bottom: 0; +} + +.sh-services-grid .sh-card-header { + display: flex; + justify-content: space-between; + align-items: center; +} + +.sh-services-grid .sh-card-title { + margin: 0; + flex: 1; +} + +.sh-services-grid .sh-card-badge { + flex-shrink: 0; + margin-left: 12px; +} + +/* === Service Status Badges === */ +.sh-services-grid .sh-status-badge { + display: inline-block; + padding: 6px 12px; + border-radius: 12px; + font-size: 13px; + font-weight: 600; + line-height: 1.4; +} + +.sh-services-grid .sh-status-ok { + background: rgba(34, 197, 94, 0.15); + color: #16a34a; +} + +.sh-services-grid .sh-status-error { + background: rgba(239, 68, 68, 0.15); + color: #dc2626; +} + +/* === Action Buttons === */ +.sh-services-grid .sh-btn { + flex: 1; + min-width: 100px; +} + +/* === Responsive === */ +@media (max-width: 768px) { + .sh-services-grid { + grid-template-columns: 1fr; + } + + .sh-services-grid .sh-card-header { + flex-direction: column; + align-items: flex-start; + gap: 12px; + } + + .sh-services-grid .sh-card-badge { + margin-left: 0; + align-self: flex-start; + } +} + +/* === Dark Mode === */ +[data-theme="dark"] .sh-services-grid .sh-status-ok { + background: rgba(34, 197, 94, 0.2); + color: #22c55e; +} + +[data-theme="dark"] .sh-services-grid .sh-status-error { + background: rgba(239, 68, 68, 0.2); + color: #ef4444; +} diff --git a/luci-app-system-hub/htdocs/luci-static/resources/view/system-hub/health.js b/luci-app-system-hub/htdocs/luci-static/resources/view/system-hub/health.js index 340d79d..1bdf263 100644 --- a/luci-app-system-hub/htdocs/luci-static/resources/view/system-hub/health.js +++ b/luci-app-system-hub/htdocs/luci-static/resources/view/system-hub/health.js @@ -2,136 +2,340 @@ 'require view'; 'require dom'; 'require ui'; +'require poll'; 'require system-hub/api as API'; 'require system-hub/theme as Theme'; -// Load CSS -document.head.appendChild(E('link', { - 'rel': 'stylesheet', - 'type': 'text/css', - 'href': L.resource('system-hub/dashboard.css') -})); - -// Initialize theme -Theme.init(); - -// Helper: Get health status info based on score -function getHealthStatus(score) { - if (score >= 90) return { status: 'excellent', label: 'Excellent', color: '#22c55e' }; - if (score >= 75) return { status: 'good', label: 'Bon', color: '#3b82f6' }; - if (score >= 50) return { status: 'warning', label: 'Attention', color: '#f59e0b' }; - return { status: 'critical', label: 'Critique', color: '#ef4444' }; -} - -// Helper: Format bytes to human-readable size -function formatBytes(bytes) { - if (!bytes) return '0 B'; - var k = 1024; - var sizes = ['B', 'KB', 'MB', 'GB', 'TB']; - var i = Math.floor(Math.log(bytes) / Math.log(k)); - return (bytes / Math.pow(k, i)).toFixed(2) + ' ' + sizes[i]; -} - return view.extend({ + healthData: null, + load: function() { - return API.getHealth(); + return Promise.all([ + API.getHealth(), + Theme.getTheme() + ]); }, render: function(data) { - var health = data; - var healthInfo = getHealthStatus(health.score || 0); - var self = this; + this.healthData = data[0] || {}; + var theme = data[1]; - var view = E('div', { 'class': 'system-hub-dashboard' }, [ + var container = E('div', { 'class': 'system-hub-health' }, [ + E('link', { 'rel': 'stylesheet', 'href': L.resource('system-hub/common.css') }), E('link', { 'rel': 'stylesheet', 'href': L.resource('system-hub/dashboard.css') }), - - // Global Score - E('div', { 'class': 'sh-card' }, [ - E('div', { 'class': 'sh-card-header' }, [ - E('div', { 'class': 'sh-card-title' }, [ E('span', { 'class': 'sh-card-title-icon' }, '💚'), 'Score de SantĂ© Global' ]), - E('div', { 'class': 'sh-card-badge' }, (health.score || 0) + '/100') - ]), - E('div', { 'class': 'sh-card-body', 'style': 'text-align: center; padding: 40px;' }, [ - E('div', { - 'class': 'sh-score-circle ' + healthInfo.status, - 'style': 'width: 120px; height: 120px; border-radius: 50%; display: flex; align-items: center; justify-content: center; margin: 0 auto 20px; font-size: 40px; font-weight: 800;' - }, (health.score || 0).toString()), - E('div', { 'style': 'font-size: 20px; font-weight: 700; margin-bottom: 8px;' }, healthInfo.label), - E('div', { 'style': 'color: #707080;' }, 'DerniĂšre vĂ©rification : ' + (health.timestamp || 'N/A')) - ]) + E('link', { 'rel': 'stylesheet', 'href': L.resource('system-hub/overview.css') }), + + // Header with health score + this.renderHeader(), + + // System Metrics Grid + E('div', { 'class': 'sh-metrics-grid' }, [ + this.renderMetricCard('CPU', this.healthData.cpu), + this.renderMetricCard('Memory', this.healthData.memory), + this.renderMetricCard('Disk', this.healthData.disk), + this.renderMetricCard('Temperature', this.healthData.temperature) ]), - - // Detailed Metrics - E('div', { 'class': 'sh-card' }, [ - E('div', { 'class': 'sh-card-header' }, [ - E('div', { 'class': 'sh-card-title' }, [ E('span', { 'class': 'sh-card-title-icon' }, '📊'), 'MĂ©triques DĂ©taillĂ©es' ]) - ]), - E('div', { 'class': 'sh-card-body' }, [ - E('div', { 'class': 'sh-health-grid' }, [ - this.renderDetailedMetric('đŸ”Č', 'CPU', health.cpu?.usage || 0, health.cpu?.status, 'Load: ' + (health.cpu?.load_1m || 'N/A')), - this.renderDetailedMetric('đŸ’Ÿ', 'MĂ©moire', health.memory?.usage || 0, health.memory?.status, formatBytes((health.memory?.used_kb || 0) * 1024) + ' utilisĂ©s'), - this.renderDetailedMetric('💿', 'Stockage', health.disk?.usage || 0, health.disk?.status, formatBytes((health.disk?.used_kb || 0) * 1024) + ' utilisĂ©s'), - this.renderDetailedMetric('đŸŒĄïž', 'TempĂ©rature', health.temperature?.value || 0, health.temperature?.status, 'Zone 0: CPU'), - this.renderDetailedMetric('🌐', 'RĂ©seau WAN', health.network?.wan_up ? 100 : 0, health.network?.status, health.network?.wan_up ? 'ConnectĂ©' : 'DĂ©connectĂ©'), - this.renderDetailedMetric('⚙', 'Services', ((health.services?.running || 0) / ((health.services?.running || 0) + (health.services?.failed || 0)) * 100) || 0, - health.services?.failed > 0 ? 'warning' : 'ok', - (health.services?.running || 0) + '/' + ((health.services?.running || 0) + (health.services?.failed || 0)) + ' actifs') - ]) - ]) + + // Network & Services Info + E('div', { 'class': 'sh-info-grid' }, [ + this.renderNetworkCard(), + this.renderServicesCard() ]), - + // Recommendations - health.recommendations && health.recommendations.length > 0 ? E('div', { 'class': 'sh-card' }, [ - E('div', { 'class': 'sh-card-header' }, [ - E('div', { 'class': 'sh-card-title' }, [ E('span', { 'class': 'sh-card-title-icon' }, '💡'), 'Recommandations' ]) - ]), - E('div', { 'class': 'sh-card-body' }, - health.recommendations.map(function(rec) { - return E('div', { 'style': 'display: flex; gap: 12px; align-items: flex-start; padding: 14px; background: rgba(245, 158, 11, 0.1); border-radius: 10px; border-left: 3px solid #f59e0b; margin-bottom: 10px;' }, [ - E('span', { 'style': 'font-size: 24px;' }, '⚠'), - E('div', {}, rec) - ]); - }) - ) - ]) : E('span'), - + this.healthData.recommendations && this.healthData.recommendations.length > 0 + ? this.renderRecommendationsCard() + : E('div'), + // Actions - E('div', { 'class': 'sh-btn-group' }, [ - E('button', { - 'class': 'sh-btn sh-btn-primary', - 'click': L.bind(this.generateReport, this) - }, [ '📋 GĂ©nĂ©rer Rapport' ]), - E('button', { 'class': 'sh-btn' }, [ '📧 Envoyer par Email' ]), - E('button', { 'class': 'sh-btn' }, [ 'đŸ“„ TĂ©lĂ©charger PDF' ]) - ]) + this.renderActionsCard() ]); - return view; + // Setup auto-refresh + poll.add(L.bind(function() { + return API.getHealth().then(L.bind(function(refreshData) { + this.healthData = refreshData || {}; + this.updateDashboard(); + }, this)); + }, this), 30); + + return container; }, - renderDetailedMetric: function(icon, label, value, status, detail) { - return E('div', { 'class': 'sh-health-metric' }, [ - E('div', { 'class': 'sh-metric-header' }, [ - E('div', { 'class': 'sh-metric-title' }, [ E('span', { 'class': 'sh-metric-icon' }, icon), label ]), - E('div', { 'class': 'sh-metric-value ' + (status || 'ok') }, value + (label === 'TempĂ©rature' ? '°C' : '%')) + renderHeader: function() { + var score = this.healthData.score || 0; + var scoreClass = score >= 80 ? 'excellent' : (score >= 60 ? 'good' : (score >= 40 ? 'warning' : 'critical')); + var scoreLabel = score >= 80 ? 'Excellent' : (score >= 60 ? 'Bon' : (score >= 40 ? 'Attention' : 'Critique')); + + return E('div', { 'class': 'sh-overview-header' }, [ + E('div', { 'class': 'sh-overview-title' }, [ + E('h2', {}, [ + E('span', { 'class': 'sh-title-icon' }, '💚'), + ' Health Monitor' + ]), + E('p', { 'class': 'sh-overview-subtitle' }, + 'Surveillance en temps rĂ©el de la santĂ© du systĂšme') ]), - E('div', { 'class': 'sh-progress-bar' }, [ - E('div', { 'class': 'sh-progress-fill ' + (status || 'ok'), 'style': 'width: ' + Math.min(value, 100) + '%' }) - ]), - E('div', { 'style': 'font-size: 10px; color: #707080; margin-top: 8px;' }, detail) + E('div', { 'class': 'sh-overview-score' }, [ + E('div', { 'class': 'sh-score-circle sh-score-' + scoreClass }, [ + E('div', { 'class': 'sh-score-value' }, score), + E('div', { 'class': 'sh-score-label' }, scoreLabel) + ]) + ]) ]); }, + renderMetricCard: function(type, data) { + if (!data) return E('div'); + + var config = this.getMetricConfig(type, data); + + return E('div', { 'class': 'sh-metric-card sh-metric-' + config.status }, [ + E('div', { 'class': 'sh-metric-header' }, [ + E('span', { 'class': 'sh-metric-icon' }, config.icon), + E('span', { 'class': 'sh-metric-title' }, config.title) + ]), + E('div', { 'class': 'sh-metric-value' }, config.value), + E('div', { 'class': 'sh-metric-progress' }, [ + E('div', { + 'class': 'sh-metric-progress-bar', + 'style': 'width: ' + config.percentage + '%; background: ' + config.color + }) + ]), + E('div', { 'class': 'sh-metric-details' }, config.details) + ]); + }, + + getMetricConfig: function(type, data) { + switch(type) { + case 'CPU': + return { + icon: 'đŸ”„', + title: 'CPU Usage', + value: (data.usage || 0) + '%', + percentage: data.usage || 0, + status: data.status || 'ok', + color: this.getStatusColor(data.usage || 0), + details: 'Load: ' + (data.load_1m || '0') + ' ‱ ' + (data.cores || 0) + ' cores' + }; + case 'Memory': + var usedMB = ((data.used_kb || 0) / 1024).toFixed(0); + var totalMB = ((data.total_kb || 0) / 1024).toFixed(0); + return { + icon: 'đŸ’Ÿ', + title: 'Memory', + value: (data.usage || 0) + '%', + percentage: data.usage || 0, + status: data.status || 'ok', + color: this.getStatusColor(data.usage || 0), + details: usedMB + ' MB / ' + totalMB + ' MB used' + }; + case 'Disk': + var usedGB = ((data.used_kb || 0) / 1024 / 1024).toFixed(1); + var totalGB = ((data.total_kb || 0) / 1024 / 1024).toFixed(1); + return { + icon: '💿', + title: 'Disk Space', + value: (data.usage || 0) + '%', + percentage: data.usage || 0, + status: data.status || 'ok', + color: this.getStatusColor(data.usage || 0), + details: usedGB + ' GB / ' + totalGB + ' GB used' + }; + case 'Temperature': + return { + icon: 'đŸŒĄïž', + title: 'Temperature', + value: (data.value || 0) + '°C', + percentage: Math.min((data.value || 0), 100), + status: data.status || 'ok', + color: this.getTempColor(data.value || 0), + details: 'Status: ' + (data.status || 'unknown') + }; + default: + return { + icon: '📊', + title: type, + value: 'N/A', + percentage: 0, + status: 'unknown', + color: '#64748b', + details: 'No data' + }; + } + }, + + getStatusColor: function(usage) { + if (usage >= 90) return '#ef4444'; + if (usage >= 75) return '#f59e0b'; + if (usage >= 50) return '#3b82f6'; + return '#22c55e'; + }, + + getTempColor: function(temp) { + if (temp >= 80) return '#ef4444'; + if (temp >= 70) return '#f59e0b'; + if (temp >= 60) return '#3b82f6'; + return '#22c55e'; + }, + + renderNetworkCard: function() { + var wan_status = this.healthData.network ? this.healthData.network.wan_up : false; + + return E('div', { 'class': 'sh-info-card' }, [ + E('div', { 'class': 'sh-info-card-header' }, [ + E('h3', {}, [ + E('span', { 'style': 'margin-right: 8px;' }, '🌐'), + 'Network Status' + ]) + ]), + E('div', { 'class': 'sh-info-card-body' }, [ + E('div', { 'class': 'sh-info-list' }, [ + this.renderInfoRow('📡', 'WAN Connection', + E('span', { + 'class': 'sh-status-badge sh-status-' + (wan_status ? 'ok' : 'error') + }, wan_status ? '✓ Connected' : '✗ Disconnected') + ), + this.renderInfoRow('🔌', 'Network Mode', + this.healthData.network ? this.healthData.network.status : 'unknown' + ) + ]) + ]) + ]); + }, + + renderServicesCard: function() { + var running = this.healthData.services ? this.healthData.services.running : 0; + var failed = this.healthData.services ? this.healthData.services.failed : 0; + var total = running + failed; + + return E('div', { 'class': 'sh-info-card' }, [ + E('div', { 'class': 'sh-info-card-header' }, [ + E('h3', {}, [ + E('span', { 'style': 'margin-right: 8px;' }, '⚙'), + 'System Services' + ]) + ]), + E('div', { 'class': 'sh-info-card-body' }, [ + E('div', { 'class': 'sh-info-list' }, [ + this.renderInfoRow('▶', 'Running', + E('span', { 'class': 'sh-status-badge sh-status-ok' }, running + ' services') + ), + this.renderInfoRow('âč', 'Failed', + failed > 0 + ? E('span', { 'class': 'sh-status-badge sh-status-error' }, failed + ' services') + : E('span', { 'class': 'sh-status-badge sh-status-ok' }, 'None') + ), + this.renderInfoRow('📊', 'Health Rate', + total > 0 ? ((running / total * 100).toFixed(0) + '%') : 'N/A' + ) + ]) + ]) + ]); + }, + + renderRecommendationsCard: function() { + return E('div', { 'class': 'sh-card' }, [ + E('div', { 'class': 'sh-card-header' }, [ + E('h3', { 'class': 'sh-card-title' }, [ + E('span', { 'class': 'sh-card-title-icon' }, '💡'), + 'Recommendations' + ]), + E('div', { 'class': 'sh-card-badge', 'style': 'background: #f59e0b;' }, + this.healthData.recommendations.length) + ]), + E('div', { 'class': 'sh-card-body' }, + this.healthData.recommendations.map(function(rec) { + return E('div', { + 'style': 'display: flex; gap: 12px; align-items: flex-start; padding: 14px; background: rgba(245, 158, 11, 0.1); border-radius: 10px; border-left: 3px solid #f59e0b; margin-bottom: 10px;' + }, [ + E('span', { 'style': 'font-size: 24px;' }, '⚠'), + E('div', { 'style': 'flex: 1; color: var(--sh-text-primary);' }, rec) + ]); + }) + ) + ]); + }, + + renderActionsCard: function() { + return E('div', { 'class': 'sh-card' }, [ + E('div', { 'class': 'sh-card-header' }, [ + E('h3', { 'class': 'sh-card-title' }, [ + E('span', { 'class': 'sh-card-title-icon' }, 'đŸ› ïž'), + 'Actions' + ]) + ]), + E('div', { 'class': 'sh-card-body' }, [ + E('div', { 'style': 'display: flex; gap: 12px; flex-wrap: wrap;' }, [ + E('button', { + 'class': 'sh-btn sh-btn-primary', + 'click': L.bind(this.generateReport, this) + }, [ + E('span', {}, '📋'), + E('span', {}, 'Generate Report') + ]), + E('button', { + 'class': 'sh-btn sh-btn-secondary', + 'click': function() { + ui.addNotification(null, E('p', '⚠ Email feature coming soon'), 'info'); + } + }, [ + E('span', {}, '📧'), + E('span', {}, 'Send Email') + ]), + E('button', { + 'class': 'sh-btn sh-btn-secondary', + 'click': function() { + ui.addNotification(null, E('p', '⚠ PDF export coming soon'), 'info'); + } + }, [ + E('span', {}, 'đŸ“„'), + E('span', {}, 'Download PDF') + ]) + ]) + ]) + ]); + }, + + renderInfoRow: function(icon, label, value) { + return E('div', { 'class': 'sh-info-row' }, [ + E('span', { 'class': 'sh-info-icon' }, icon), + E('span', { 'class': 'sh-info-label' }, label), + E('span', { 'class': 'sh-info-value' }, value) + ]); + }, + + updateDashboard: function() { + var metricsGrid = document.querySelector('.sh-metrics-grid'); + if (metricsGrid) { + dom.content(metricsGrid, [ + this.renderMetricCard('CPU', this.healthData.cpu), + this.renderMetricCard('Memory', this.healthData.memory), + this.renderMetricCard('Disk', this.healthData.disk), + this.renderMetricCard('Temperature', this.healthData.temperature) + ]); + } + + // Update health score + var scoreValue = document.querySelector('.sh-score-value'); + var scoreCircle = document.querySelector('.sh-score-circle'); + if (scoreValue && scoreCircle) { + var score = this.healthData.score || 0; + var scoreClass = score >= 80 ? 'excellent' : (score >= 60 ? 'good' : (score >= 40 ? 'warning' : 'critical')); + scoreValue.textContent = score; + scoreCircle.className = 'sh-score-circle sh-score-' + scoreClass; + } + }, + generateReport: function() { - ui.showModal(_('GĂ©nĂ©ration Rapport'), [ - E('p', {}, 'GĂ©nĂ©ration du rapport de santĂ©...'), + ui.showModal(_('Generating Report'), [ + E('p', {}, 'Generating health report...'), E('div', { 'class': 'spinning' }) ]); - // Stub: Report generation not yet implemented setTimeout(function() { ui.hideModal(); - ui.addNotification(null, E('p', {}, '⚠ Report generation feature coming soon'), 'info'); + ui.addNotification(null, E('p', '⚠ Report generation feature coming soon'), 'info'); }, 1000); }, diff --git a/luci-app-system-hub/htdocs/luci-static/resources/view/system-hub/logs.js b/luci-app-system-hub/htdocs/luci-static/resources/view/system-hub/logs.js index 723e906..141bf17 100644 --- a/luci-app-system-hub/htdocs/luci-static/resources/view/system-hub/logs.js +++ b/luci-app-system-hub/htdocs/luci-static/resources/view/system-hub/logs.js @@ -1,103 +1,274 @@ 'use strict'; 'require view'; 'require ui'; +'require dom'; 'require system-hub/api as API'; 'require system-hub/theme as Theme'; -// Load CSS -document.head.appendChild(E('link', { - 'rel': 'stylesheet', - 'type': 'text/css', - 'href': L.resource('system-hub/dashboard.css') -})); +return view.extend({ + logs: [], + currentFilter: 'all', + searchQuery: '', + lineCount: 100, -// Initialize theme -Theme.init(); - -return L.view.extend({ load: function() { - return API.getLogs(100, ''); + return Promise.all([ + API.getLogs(100, ''), + Theme.getTheme() + ]); }, - render: function(logs) { - var v = E('div', { 'class': 'cbi-map' }, [ - E('h2', {}, _('System Logs')), - E('div', { 'class': 'cbi-map-descr' }, _('View and filter system logs')) + render: function(data) { + this.logs = data[0] || []; + var theme = data[1]; + + var container = E('div', { 'class': 'system-hub-logs' }, [ + E('link', { 'rel': 'stylesheet', 'href': L.resource('system-hub/common.css') }), + E('link', { 'rel': 'stylesheet', 'href': L.resource('system-hub/dashboard.css') }), + + // Header + this.renderHeader(), + + // Controls + this.renderControls(), + + // Filter tabs + this.renderFilterTabs(), + + // Log viewer + E('div', { 'class': 'sh-card' }, [ + E('div', { 'class': 'sh-card-header' }, [ + E('h3', { 'class': 'sh-card-title' }, [ + E('span', { 'class': 'sh-card-title-icon' }, '📟'), + 'Log Output' + ]), + E('div', { 'class': 'sh-card-badge' }, this.getFilteredLogs().length + ' lines') + ]), + E('div', { 'class': 'sh-card-body', 'style': 'padding: 0;' }, [ + E('div', { 'id': 'log-container' }) + ]) + ]) ]); - var section = E('div', { 'class': 'cbi-section' }); - - // Filter controls - var controlsDiv = E('div', { 'style': 'margin-bottom: 15px; display: flex; gap: 10px; align-items: center;' }); - - var filterInput = E('input', { - 'type': 'text', - 'class': 'cbi-input-text', - 'placeholder': _('Filter logs...'), - 'style': 'flex: 1;' - }); - - var linesSelect = E('select', { 'class': 'cbi-input-select' }, [ - E('option', { 'value': '50' }, '50 lines'), - E('option', { 'value': '100', 'selected': '' }, '100 lines'), - E('option', { 'value': '200' }, '200 lines'), - E('option', { 'value': '500' }, '500 lines'), - E('option', { 'value': '1000' }, '1000 lines') - ]); - - var refreshBtn = E('button', { - 'class': 'cbi-button cbi-button-action', - 'click': L.bind(function() { - this.refreshLogs(filterInput.value, parseInt(linesSelect.value)); - }, this) - }, _('Refresh')); - - var clearBtn = E('button', { - 'class': 'cbi-button cbi-button-neutral', - 'click': function() { - filterInput.value = ''; - } - }, _('Clear Filter')); - - controlsDiv.appendChild(filterInput); - controlsDiv.appendChild(linesSelect); - controlsDiv.appendChild(refreshBtn); - controlsDiv.appendChild(clearBtn); - - section.appendChild(controlsDiv); - - // Log display - var logContainer = E('div', { 'id': 'log-container' }); - section.appendChild(logContainer); - // Initial render - this.renderLogs(logContainer, logs); + this.updateLogDisplay(); - v.appendChild(section); - - return v; + return container; }, - renderLogs: function(container, logs) { - var logsText = logs.length > 0 ? logs.join('\n') : _('No logs available'); + renderHeader: function() { + var stats = this.getLogStats(); - L.dom.content(container, [ - E('pre', { - 'style': 'background: #000; color: #0f0; padding: 15px; overflow: auto; max-height: 600px; font-size: 11px; font-family: monospace; border-radius: 5px;' - }, logsText) + return E('div', { 'class': 'sh-page-header' }, [ + E('div', {}, [ + E('h2', { 'class': 'sh-page-title' }, [ + E('span', { 'class': 'sh-page-title-icon' }, '📋'), + 'System Logs' + ]), + E('p', { 'class': 'sh-page-subtitle' }, + 'View and filter system logs in real-time') + ]), + E('div', { 'class': 'sh-stats-grid' }, [ + E('div', { 'class': 'sh-stat-badge' }, [ + E('div', { 'class': 'sh-stat-value' }, stats.total), + E('div', { 'class': 'sh-stat-label' }, 'Total Lines') + ]), + E('div', { 'class': 'sh-stat-badge' }, [ + E('div', { 'class': 'sh-stat-value', 'style': 'color: #ef4444;' }, stats.errors), + E('div', { 'class': 'sh-stat-label' }, 'Errors') + ]), + E('div', { 'class': 'sh-stat-badge' }, [ + E('div', { 'class': 'sh-stat-value', 'style': 'color: #f59e0b;' }, stats.warnings), + E('div', { 'class': 'sh-stat-label' }, 'Warnings') + ]) + ]) ]); }, - refreshLogs: function(filter, lines) { + renderControls: function() { + var self = this; + + return E('div', { 'style': 'display: flex; gap: 12px; margin-bottom: 24px; flex-wrap: wrap;' }, [ + // Search box + E('input', { + 'type': 'text', + 'class': 'cbi-input-text', + 'placeholder': '🔍 Search logs...', + 'style': 'flex: 1; min-width: 250px; padding: 12px 16px; border-radius: 8px; border: 1px solid var(--sh-border); background: var(--sh-bg-card); color: var(--sh-text-primary); font-size: 14px;', + 'input': function(ev) { + self.searchQuery = ev.target.value.toLowerCase(); + self.updateLogDisplay(); + } + }), + // Line count selector + E('select', { + 'class': 'cbi-input-select', + 'style': 'padding: 12px 16px; border-radius: 8px; border: 1px solid var(--sh-border); background: var(--sh-bg-card); color: var(--sh-text-primary); font-size: 14px;', + 'change': function(ev) { + self.lineCount = parseInt(ev.target.value); + self.refreshLogs(); + } + }, [ + E('option', { 'value': '50' }, '50 lines'), + E('option', { 'value': '100', 'selected': '' }, '100 lines'), + E('option', { 'value': '200' }, '200 lines'), + E('option', { 'value': '500' }, '500 lines'), + E('option', { 'value': '1000' }, '1000 lines') + ]), + // Refresh button + E('button', { + 'class': 'sh-btn sh-btn-primary', + 'click': L.bind(this.refreshLogs, this) + }, [ + E('span', {}, '🔄'), + E('span', {}, 'Refresh') + ]), + // Download button + E('button', { + 'class': 'sh-btn sh-btn-secondary', + 'click': function() { + self.downloadLogs(); + } + }, [ + E('span', {}, 'đŸ“„'), + E('span', {}, 'Download') + ]) + ]); + }, + + renderFilterTabs: function() { + var stats = this.getLogStats(); + + return E('div', { 'class': 'sh-filter-tabs' }, [ + this.createFilterTab('all', '📋', 'All Logs', stats.total), + this.createFilterTab('error', '🔮', 'Errors', stats.errors), + this.createFilterTab('warning', '🟡', 'Warnings', stats.warnings), + this.createFilterTab('info', 'đŸ””', 'Info', stats.info) + ]); + }, + + createFilterTab: function(filter, icon, label, count) { + var self = this; + var isActive = this.currentFilter === filter; + + return E('div', { + 'class': 'sh-filter-tab' + (isActive ? ' active' : ''), + 'click': function() { + self.currentFilter = filter; + self.updateFilterTabs(); + self.updateLogDisplay(); + } + }, [ + E('span', { 'class': 'sh-tab-icon' }, icon), + E('span', { 'class': 'sh-tab-label' }, label + ' (' + count + ')') + ]); + }, + + updateFilterTabs: function() { + var tabs = document.querySelectorAll('.sh-filter-tab'); + tabs.forEach(function(tab, index) { + var filters = ['all', 'error', 'warning', 'info']; + if (filters[index] === this.currentFilter) { + tab.classList.add('active'); + } else { + tab.classList.remove('active'); + } + }.bind(this)); + }, + + updateLogDisplay: function() { + var container = document.getElementById('log-container'); + if (!container) return; + + var filtered = this.getFilteredLogs(); + var logsText = filtered.length > 0 ? filtered.join('\n') : 'No logs available'; + + dom.content(container, [ + E('pre', { + 'style': 'background: var(--sh-bg-tertiary); color: var(--sh-text-primary); padding: 20px; overflow: auto; max-height: 600px; font-size: 12px; font-family: "Courier New", monospace; border-radius: 0; margin: 0; line-height: 1.5;' + }, logsText) + ]); + + // Update badge + var badge = document.querySelector('.sh-card-badge'); + if (badge) { + badge.textContent = filtered.length + ' lines'; + } + }, + + getFilteredLogs: function() { + return this.logs.filter(function(line) { + var lineLower = line.toLowerCase(); + + // Apply filter + var matchesFilter = true; + switch (this.currentFilter) { + case 'error': + matchesFilter = lineLower.includes('error') || lineLower.includes('err') || lineLower.includes('fail'); + break; + case 'warning': + matchesFilter = lineLower.includes('warn') || lineLower.includes('warning'); + break; + case 'info': + matchesFilter = lineLower.includes('info') || lineLower.includes('notice'); + break; + } + + // Apply search + var matchesSearch = !this.searchQuery || lineLower.includes(this.searchQuery); + + return matchesFilter && matchesSearch; + }.bind(this)); + }, + + getLogStats: function() { + var stats = { + total: this.logs.length, + errors: 0, + warnings: 0, + info: 0 + }; + + this.logs.forEach(function(line) { + var lineLower = line.toLowerCase(); + if (lineLower.includes('error') || lineLower.includes('err') || lineLower.includes('fail')) { + stats.errors++; + } else if (lineLower.includes('warn') || lineLower.includes('warning')) { + stats.warnings++; + } else if (lineLower.includes('info') || lineLower.includes('notice')) { + stats.info++; + } + }); + + return stats; + }, + + refreshLogs: function() { ui.showModal(_('Loading Logs'), [ E('p', { 'class': 'spinning' }, _('Fetching logs...')) ]); - API.getLogs(lines, filter).then(L.bind(function(logs) { + API.getLogs(this.lineCount, '').then(L.bind(function(logs) { ui.hideModal(); - var container = document.getElementById('log-container'); - if (container) { - this.renderLogs(container, logs); + this.logs = logs || []; + this.updateLogDisplay(); + + // Update stats + var stats = this.getLogStats(); + var statBadges = document.querySelectorAll('.sh-stat-value'); + if (statBadges.length >= 3) { + statBadges[0].textContent = stats.total; + statBadges[1].textContent = stats.errors; + statBadges[2].textContent = stats.warnings; + } + + // Update filter tabs counts + var tabs = document.querySelectorAll('.sh-tab-label'); + if (tabs.length >= 4) { + tabs[0].textContent = 'All Logs (' + stats.total + ')'; + tabs[1].textContent = 'Errors (' + stats.errors + ')'; + tabs[2].textContent = 'Warnings (' + stats.warnings + ')'; + tabs[3].textContent = 'Info (' + stats.info + ')'; } }, this)).catch(function(err) { ui.hideModal(); @@ -105,6 +276,19 @@ return L.view.extend({ }); }, + downloadLogs: function() { + var filtered = this.getFilteredLogs(); + var content = filtered.join('\n'); + var blob = new Blob([content], { type: 'text/plain' }); + var url = URL.createObjectURL(blob); + var a = document.createElement('a'); + a.href = url; + a.download = 'system-logs-' + new Date().toISOString().split('T')[0] + '.txt'; + a.click(); + URL.revokeObjectURL(url); + ui.addNotification(null, E('p', '✓ Logs downloaded'), 'info'); + }, + handleSaveApply: null, handleSave: null, handleReset: null diff --git a/luci-app-system-hub/htdocs/luci-static/resources/view/system-hub/overview.js b/luci-app-system-hub/htdocs/luci-static/resources/view/system-hub/overview.js index 92b4eae..d637169 100644 --- a/luci-app-system-hub/htdocs/luci-static/resources/view/system-hub/overview.js +++ b/luci-app-system-hub/htdocs/luci-static/resources/view/system-hub/overview.js @@ -25,12 +25,16 @@ return view.extend({ var theme = data[2]; var container = E('div', { 'class': 'system-hub-dashboard' }, [ + E('link', { 'rel': 'stylesheet', 'href': L.resource('system-hub/common.css') }), E('link', { 'rel': 'stylesheet', 'href': L.resource('system-hub/dashboard.css') }), E('link', { 'rel': 'stylesheet', 'href': L.resource('system-hub/overview.css') }), // Header this.renderHeader(), + // Stats Overview (like SecuBox) + this.renderStatsOverview(), + // Health Metrics Cards E('div', { 'class': 'sh-metrics-grid' }, [ this.renderMetricCard('CPU', this.healthData.cpu), @@ -66,24 +70,53 @@ return view.extend({ var score = this.healthData.score || 0; var scoreClass = score >= 80 ? 'excellent' : (score >= 60 ? 'good' : (score >= 40 ? 'warning' : 'critical')); - return E('div', { 'class': 'sh-overview-header' }, [ - E('div', { 'class': 'sh-overview-title' }, [ - E('h2', {}, [ - E('span', { 'class': 'sh-title-icon' }, 'đŸ–„ïž'), - ' System Overview' + return E('div', { 'class': 'sh-dashboard-header' }, [ + E('div', { 'class': 'sh-dashboard-header-content' }, [ + E('div', {}, [ + E('h2', {}, 'đŸ–„ïž System Hub'), + E('p', { 'class': 'sh-dashboard-subtitle' }, 'System Monitoring & Management Center') ]), - E('p', { 'class': 'sh-overview-subtitle' }, - this.sysInfo.hostname + ' ‱ ' + this.sysInfo.model) - ]), - E('div', { 'class': 'sh-overview-score' }, [ - E('div', { 'class': 'sh-score-circle sh-score-' + scoreClass }, [ - E('div', { 'class': 'sh-score-value' }, score), - E('div', { 'class': 'sh-score-label' }, 'Health Score') + E('div', { 'class': 'sh-dashboard-header-info' }, [ + E('span', { 'class': 'sh-dashboard-badge sh-dashboard-badge-version' }, + 'v0.2.2'), + E('span', { 'class': 'sh-dashboard-badge' }, + '⏱ ' + (this.sysInfo.uptime_formatted || '0d 0h 0m')), + E('span', { 'class': 'sh-dashboard-badge' }, + 'đŸ–„ïž ' + (this.sysInfo.hostname || 'OpenWrt')) ]) ]) ]); }, + renderStatsOverview: function() { + var score = this.healthData.score || 0; + var scoreClass = score >= 80 ? 'excellent' : (score >= 60 ? 'good' : (score >= 40 ? 'warning' : 'critical')); + var scoreLabel = score >= 80 ? 'Excellent' : (score >= 60 ? 'Good' : (score >= 40 ? 'Warning' : 'Critical')); + + return E('div', { 'class': 'sh-stats-overview-grid' }, [ + E('div', { 'class': 'sh-stat-overview-card sh-stat-' + scoreClass }, [ + E('div', { 'class': 'sh-stat-overview-value' }, score), + E('div', { 'class': 'sh-stat-overview-label' }, 'Health Score'), + E('div', { 'class': 'sh-stat-overview-status' }, scoreLabel) + ]), + E('div', { 'class': 'sh-stat-overview-card sh-stat-cpu' }, [ + E('div', { 'class': 'sh-stat-overview-icon' }, 'đŸ”„'), + E('div', { 'class': 'sh-stat-overview-value' }, (this.healthData.cpu?.usage || 0) + '%'), + E('div', { 'class': 'sh-stat-overview-label' }, 'CPU Usage') + ]), + E('div', { 'class': 'sh-stat-overview-card sh-stat-memory' }, [ + E('div', { 'class': 'sh-stat-overview-icon' }, 'đŸ’Ÿ'), + E('div', { 'class': 'sh-stat-overview-value' }, (this.healthData.memory?.usage || 0) + '%'), + E('div', { 'class': 'sh-stat-overview-label' }, 'Memory Usage') + ]), + E('div', { 'class': 'sh-stat-overview-card sh-stat-disk' }, [ + E('div', { 'class': 'sh-stat-overview-icon' }, '💿'), + E('div', { 'class': 'sh-stat-overview-value' }, (this.healthData.disk?.usage || 0) + '%'), + E('div', { 'class': 'sh-stat-overview-label' }, 'Disk Usage') + ]) + ]); + }, + renderMetricCard: function(type, data) { if (!data) return E('div'); diff --git a/luci-app-system-hub/htdocs/luci-static/resources/view/system-hub/services.js b/luci-app-system-hub/htdocs/luci-static/resources/view/system-hub/services.js index 21d1b99..068a657 100644 --- a/luci-app-system-hub/htdocs/luci-static/resources/view/system-hub/services.js +++ b/luci-app-system-hub/htdocs/luci-static/resources/view/system-hub/services.js @@ -1,116 +1,292 @@ 'use strict'; 'require view'; 'require ui'; +'require dom'; 'require system-hub/api as API'; 'require system-hub/theme as Theme'; -// Load CSS -document.head.appendChild(E('link', { - 'rel': 'stylesheet', - 'type': 'text/css', - 'href': L.resource('system-hub/dashboard.css') -})); +return view.extend({ + services: [], + currentFilter: 'all', + searchQuery: '', -// Initialize theme -Theme.init(); - -return L.view.extend({ load: function() { - return API.listServices(); + return Promise.all([ + API.listServices(), + Theme.getTheme() + ]); }, - render: function(services) { - var v = E('div', { 'class': 'cbi-map' }, [ - E('h2', {}, _('System Services')), - E('div', { 'class': 'cbi-map-descr' }, _('Manage system services: start, stop, restart, enable, or disable')) + render: function(data) { + this.services = data[0] || []; + var theme = data[1]; + + var container = E('div', { 'class': 'system-hub-services' }, [ + E('link', { 'rel': 'stylesheet', 'href': L.resource('system-hub/common.css') }), + E('link', { 'rel': 'stylesheet', 'href': L.resource('system-hub/dashboard.css') }), + E('link', { 'rel': 'stylesheet', 'href': L.resource('system-hub/services.css') }), + + // Header with stats + this.renderHeader(), + + // Filter tabs + this.renderFilterTabs(), + + // Search box + this.renderSearchBox(), + + // Services grid + E('div', { 'class': 'sh-services-grid', 'id': 'services-grid' }) ]); - var section = E('div', { 'class': 'cbi-section' }, [ - E('h3', {}, _('Service List')) - ]); + // Initial render + this.updateServicesGrid(); - if (services.length === 0) { - section.appendChild(E('p', { 'style': 'text-align: center; font-style: italic; padding: 20px;' }, - _('No services found'))); - v.appendChild(section); - return v; - } + return container; + }, - var table = E('table', { 'class': 'table' }, [ - E('tr', { 'class': 'tr table-titles' }, [ - E('th', { 'class': 'th' }, _('Service Name')), - E('th', { 'class': 'th' }, _('Status')), - E('th', { 'class': 'th' }, _('Autostart')), - E('th', { 'class': 'th' }, _('Actions')) + renderHeader: function() { + var stats = this.getStats(); + + return E('div', { 'class': 'sh-page-header' }, [ + E('div', {}, [ + E('h2', { 'class': 'sh-page-title' }, [ + E('span', { 'class': 'sh-page-title-icon' }, '⚙'), + 'System Services' + ]), + E('p', { 'class': 'sh-page-subtitle' }, + 'Manage system services: start, stop, restart, enable, or disable') + ]), + E('div', { 'class': 'sh-stats-grid' }, [ + E('div', { 'class': 'sh-stat-badge' }, [ + E('div', { 'class': 'sh-stat-value', 'style': 'color: #22c55e;' }, stats.running), + E('div', { 'class': 'sh-stat-label' }, 'Running') + ]), + E('div', { 'class': 'sh-stat-badge' }, [ + E('div', { 'class': 'sh-stat-value', 'style': 'color: #ef4444;' }, stats.stopped), + E('div', { 'class': 'sh-stat-label' }, 'Stopped') + ]), + E('div', { 'class': 'sh-stat-badge' }, [ + E('div', { 'class': 'sh-stat-value', 'style': 'color: #6366f1;' }, stats.enabled), + E('div', { 'class': 'sh-stat-label' }, 'Enabled') + ]), + E('div', { 'class': 'sh-stat-badge' }, [ + E('div', { 'class': 'sh-stat-value' }, stats.total), + E('div', { 'class': 'sh-stat-label' }, 'Total') + ]) ]) ]); + }, - services.forEach(L.bind(function(service) { - var statusColor = service.running ? 'green' : 'red'; - var statusText = service.running ? '● Running' : '○ Stopped'; - var enabledText = service.enabled ? '✓ Enabled' : '✗ Disabled'; + renderFilterTabs: function() { + var self = this; + var stats = this.getStats(); - var actionsDiv = E('div', { 'style': 'display: flex; gap: 5px;' }); + return E('div', { 'class': 'sh-filter-tabs' }, [ + this.createFilterTab('all', '📋', 'All Services', stats.total), + this.createFilterTab('running', '▶', 'Running', stats.running), + this.createFilterTab('stopped', 'âč', 'Stopped', stats.stopped), + this.createFilterTab('enabled', '✓', 'Enabled', stats.enabled), + this.createFilterTab('disabled', '✗', 'Disabled', stats.disabled) + ]); + }, - // Start button - if (!service.running) { - actionsDiv.appendChild(E('button', { - 'class': 'cbi-button cbi-button-positive', - 'click': L.bind(function(service_name, ev) { - this.performAction(service_name, 'start'); - }, this, service.name) - }, _('Start'))); + createFilterTab: function(filter, icon, label, count) { + var self = this; + var isActive = this.currentFilter === filter; + + return E('div', { + 'class': 'sh-filter-tab' + (isActive ? ' active' : ''), + 'click': function() { + self.currentFilter = filter; + self.updateFilterTabs(); + self.updateServicesGrid(); } + }, [ + E('span', { 'class': 'sh-tab-icon' }, icon), + E('span', { 'class': 'sh-tab-label' }, label + ' (' + count + ')') + ]); + }, - // Stop button - if (service.running) { - actionsDiv.appendChild(E('button', { - 'class': 'cbi-button cbi-button-negative', - 'click': L.bind(function(service_name, ev) { - this.performAction(service_name, 'stop'); - }, this, service.name) - }, _('Stop'))); - } + renderSearchBox: function() { + var self = this; - // Restart button - actionsDiv.appendChild(E('button', { - 'class': 'cbi-button cbi-button-action', - 'click': L.bind(function(service_name, ev) { - this.performAction(service_name, 'restart'); - }, this, service.name) - }, _('Restart'))); + return E('div', { 'style': 'margin-bottom: 24px;' }, [ + E('input', { + 'type': 'text', + 'class': 'cbi-input-text', + 'placeholder': '🔍 Search services...', + 'style': 'width: 100%; padding: 12px 16px; border-radius: 8px; border: 1px solid var(--sh-border); background: var(--sh-bg-card); color: var(--sh-text-primary); font-size: 14px;', + 'input': function(ev) { + self.searchQuery = ev.target.value.toLowerCase(); + self.updateServicesGrid(); + } + }) + ]); + }, - // Enable/Disable button - if (service.enabled) { - actionsDiv.appendChild(E('button', { - 'class': 'cbi-button cbi-button-neutral', - 'click': L.bind(function(service_name, ev) { - this.performAction(service_name, 'disable'); - }, this, service.name) - }, _('Disable'))); + updateFilterTabs: function() { + var tabs = document.querySelectorAll('.sh-filter-tab'); + tabs.forEach(function(tab, index) { + var filters = ['all', 'running', 'stopped', 'enabled', 'disabled']; + if (filters[index] === this.currentFilter) { + tab.classList.add('active'); } else { - actionsDiv.appendChild(E('button', { - 'class': 'cbi-button cbi-button-apply', - 'click': L.bind(function(service_name, ev) { - this.performAction(service_name, 'enable'); - }, this, service.name) - }, _('Enable'))); + tab.classList.remove('active'); + } + }.bind(this)); + }, + + updateServicesGrid: function() { + var grid = document.getElementById('services-grid'); + if (!grid) return; + + var filtered = this.getFilteredServices(); + + if (filtered.length === 0) { + dom.content(grid, [ + E('div', { 'class': 'sh-empty-state' }, [ + E('div', { 'class': 'sh-empty-icon' }, '📭'), + E('div', { 'class': 'sh-empty-text' }, + this.searchQuery ? 'No services match your search' : 'No services found') + ]) + ]); + return; + } + + dom.content(grid, filtered.map(this.renderServiceCard, this)); + }, + + getFilteredServices: function() { + return this.services.filter(function(service) { + // Apply filter + var matchesFilter = true; + switch (this.currentFilter) { + case 'running': + matchesFilter = service.running; + break; + case 'stopped': + matchesFilter = !service.running; + break; + case 'enabled': + matchesFilter = service.enabled; + break; + case 'disabled': + matchesFilter = !service.enabled; + break; } - table.appendChild(E('tr', { 'class': 'tr' }, [ - E('td', { 'class': 'td' }, E('strong', {}, service.name)), - E('td', { 'class': 'td' }, [ - E('span', { 'style': 'color: ' + statusColor + '; font-weight: bold;' }, statusText) + // Apply search + var matchesSearch = !this.searchQuery || + service.name.toLowerCase().includes(this.searchQuery); + + return matchesFilter && matchesSearch; + }.bind(this)); + }, + + renderServiceCard: function(service) { + var statusClass = service.running ? 'ok' : 'error'; + var statusIcon = service.running ? '▶' : 'âč'; + var statusText = service.running ? 'Running' : 'Stopped'; + var enabledIcon = service.enabled ? '✓' : '✗'; + var enabledText = service.enabled ? 'Enabled' : 'Disabled'; + + return E('div', { 'class': 'sh-card' }, [ + E('div', { 'class': 'sh-card-header' }, [ + E('h3', { 'class': 'sh-card-title' }, [ + E('span', { 'class': 'sh-card-title-icon' }, '⚙'), + service.name ]), - E('td', { 'class': 'td' }, enabledText), - E('td', { 'class': 'td' }, actionsDiv) + E('div', { 'class': 'sh-card-badge sh-status-badge sh-status-' + statusClass }, [ + statusIcon + ' ' + statusText + ]) + ]), + E('div', { 'class': 'sh-card-body' }, [ + E('div', { 'style': 'display: flex; align-items: center; gap: 8px; margin-bottom: 16px;' }, [ + E('span', { 'style': 'font-size: 16px;' }, enabledIcon), + E('span', { 'style': 'font-weight: 600; color: var(--sh-text-secondary);' }, + 'Autostart: ' + enabledText) + ]), + E('div', { 'style': 'display: flex; gap: 8px; flex-wrap: wrap;' }, + this.renderActionButtons(service) + ) + ]) + ]); + }, + + renderActionButtons: function(service) { + var buttons = []; + + // Start button (only if stopped) + if (!service.running) { + buttons.push(E('button', { + 'class': 'sh-btn sh-btn-success', + 'click': L.bind(this.performAction, this, service.name, 'start') + }, [ + E('span', {}, '▶'), + E('span', {}, 'Start') ])); - }, this)); + } - section.appendChild(table); - v.appendChild(section); + // Stop button (only if running) + if (service.running) { + buttons.push(E('button', { + 'class': 'sh-btn sh-btn-danger', + 'click': L.bind(this.performAction, this, service.name, 'stop') + }, [ + E('span', {}, 'âč'), + E('span', {}, 'Stop') + ])); + } - return v; + // Restart button + buttons.push(E('button', { + 'class': 'sh-btn sh-btn-warning', + 'click': L.bind(this.performAction, this, service.name, 'restart') + }, [ + E('span', {}, '🔄'), + E('span', {}, 'Restart') + ])); + + // Enable/Disable button + if (service.enabled) { + buttons.push(E('button', { + 'class': 'sh-btn sh-btn-secondary', + 'click': L.bind(this.performAction, this, service.name, 'disable') + }, [ + E('span', {}, '✗'), + E('span', {}, 'Disable') + ])); + } else { + buttons.push(E('button', { + 'class': 'sh-btn sh-btn-primary', + 'click': L.bind(this.performAction, this, service.name, 'enable') + }, [ + E('span', {}, '✓'), + E('span', {}, 'Enable') + ])); + } + + return buttons; + }, + + getStats: function() { + var stats = { + total: this.services.length, + running: 0, + stopped: 0, + enabled: 0, + disabled: 0 + }; + + this.services.forEach(function(service) { + if (service.running) stats.running++; + else stats.stopped++; + if (service.enabled) stats.enabled++; + else stats.disabled++; + }); + + return stats; }, performAction: function(service, action) {