release: v0.2.2 - Design System v0.3.0 & Comprehensive Documentation

🎨 Design System v0.3.0 (Demo-inspired)
- New dark palette: #0a0a0f, #6366f1→#8b5cf6 gradients
- Typography: Inter + JetBrains Mono
- Compact stats grid (130px min)
- Gradient text effects with background-clip
- Sticky navigation tabs
- Enhanced card borders and hover effects

📚 Comprehensive Documentation Suite
- DEVELOPMENT-GUIDELINES.md (33KB, 900+ lines)
  - 9 major sections: Design, Architecture, RPCD, ACL, JS, CSS, Errors, Validation, Deployment
  - Complete code templates and best practices
  - Common error diagnostics and solutions
- QUICK-START.md (6.4KB)
  - 8 critical rules for immediate reference
  - Quick code templates
  - Error quick fixes table
- deploy-module-template.sh (8.1KB)
  - Standardized deployment with automatic backup
  - Permission fixes, cache clearing, verification
- Updated CLAUDE.md, README.md with documentation index
- Updated .claude/README.md to v2.0

🔄 Version Updates
- luci-app-secubox: 0.1.2 → 0.2.2
- luci-app-system-hub: 0.1.1 → 0.2.2
- Updated all version strings (api.js, overview.js, CSS files)

🎯 CSS Enhancements
- common.css: Complete rewrite with demo palette
- overview.css: Dashboard header with gradient
- services.css: Updated version to 0.2.2
- components.css: Updated version to 0.2.2

🔧 Critical Rules Documented
1. RPCD naming: file = ubus object (luci. prefix)
2. Menu path = view file location
3. Permissions: 755 (RPCD), 644 (CSS/JS)
4. ALWAYS run validate-modules.sh
5. CSS variables only (no hardcode)
6. Dark mode mandatory
7. Typography: Inter + JetBrains Mono
8. Gradients: --sh-primary → --sh-primary-end

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
CyberMind-FR 2025-12-26 18:55:19 +01:00
parent 4e2763190d
commit 8e53825ad5
21 changed files with 4299 additions and 757 deletions

View File

@ -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)<br/>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)<br/>Règles critiques, commandes rapides, templates de code | Référence quotidienne |
| **[CLAUDE.md](../CLAUDE.md)** | 🏗️ **ARCHITECTURE** (17KB)<br/>Build OpenWrt, structure fichiers, CI/CD | Build et architecture |
| **[README.md](../README.md)** | 📘 **VUE D'ENSEMBLE** (18KB)<br/>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 <module-name>` |
| **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-<module-name>/
├── Makefile # Package OpenWrt
├── README.md # Documentation module
├── htdocs/luci-static/resources/
│ ├── {module}/
│ │ └── api.js
│ └── view/{module}/
│ └── overview.js
│ ├── view/<module-name>/
│ │ ├── overview.js # Vue principale
│ │ ├── settings.js # Configuration (optionnel)
│ │ └── *.js # Autres vues
│ └── <module-name>/
│ ├── 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.<module-name> ⚠️ MUST match ubus object!
└── usr/share/
├── luci/menu.d/
│ └── luci-app-<module-name>.json
└── rpcd/acl.d/
└── luci-app-<module-name>.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

View File

@ -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)"
]
}
}

View File

@ -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.

1443
DEVELOPMENT-GUIDELINES.md Normal file

File diff suppressed because it is too large Load Diff

284
QUICK-START.md Normal file
View File

@ -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-<module>/
├── Makefile
├── htdocs/luci-static/resources/
│ ├── view/<module>/
│ │ └── overview.js
│ └── <module>/
│ ├── api.js
│ ├── common.css
│ └── overview.css
└── root/
├── usr/libexec/rpcd/
│ └── luci.<module> ⚠️ MUST match ubus object!
└── usr/share/
├── luci/menu.d/
│ └── luci-app-<module>.json
└── rpcd/acl.d/
└── luci-app-<module>.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 <module>/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

View File

@ -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.

268
deploy-module-template.sh Executable file
View File

@ -0,0 +1,268 @@
#!/bin/bash
# Template de déploiement standardisé pour modules SecuBox/System Hub
# Usage: ./deploy-module-template.sh <module-name>
#
# 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 <module-name>"
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

View File

@ -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 <contact@cybermind.fr>

View File

@ -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',

View File

@ -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,

View File

@ -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));
}
});

View File

@ -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 <contact@cybermind.fr>

View File

@ -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',

View File

@ -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);
}

View File

@ -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 === */

View File

@ -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));
}

View File

@ -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;
}

View File

@ -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') }),
E('link', { 'rel': 'stylesheet', 'href': L.resource('system-hub/overview.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'))
])
// 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' }, [
this.healthData.recommendations && this.healthData.recommendations.length > 0
? this.renderRecommendationsCard()
: E('div'),
// Actions
this.renderActionsCard()
]);
// 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;
},
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-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('div', { 'class': 'sh-card-title' }, [ E('span', { 'class': 'sh-card-title-icon' }, '💡'), 'Recommandations' ])
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' },
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;' }, [
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', {}, rec)
E('div', { 'style': 'flex: 1; color: var(--sh-text-primary);' }, rec)
]);
})
)
]) : E('span'),
]);
},
// Actions
E('div', { 'class': 'sh-btn-group' }, [
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)
}, [ '📋 Générer Rapport' ]),
E('button', { 'class': 'sh-btn' }, [ '📧 Envoyer par Email' ]),
E('button', { 'class': 'sh-btn' }, [ '📥 Télécharger PDF' ])
}, [
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')
])
])
])
]);
return view;
},
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' : '%'))
]),
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)
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);
},

View File

@ -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' });
// Initial render
this.updateLogDisplay();
// Filter controls
var controlsDiv = E('div', { 'style': 'margin-bottom: 15px; display: flex; gap: 10px; align-items: center;' });
return container;
},
var filterInput = E('input', {
renderHeader: function() {
var stats = this.getLogStats();
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')
])
])
]);
},
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': _('Filter logs...'),
'style': 'flex: 1;'
});
var linesSelect = E('select', { 'class': 'cbi-input-select' }, [
'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')
]);
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',
]),
// 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() {
filterInput.value = '';
self.downloadLogs();
}
}, _('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);
v.appendChild(section);
return v;
}, [
E('span', {}, '📥'),
E('span', {}, 'Download')
])
]);
},
renderLogs: function(container, logs) {
var logsText = logs.length > 0 ? logs.join('\n') : _('No logs available');
renderFilterTabs: function() {
var stats = this.getLogStats();
L.dom.content(container, [
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: #000; color: #0f0; padding: 15px; overflow: auto; max-height: 600px; font-size: 11px; font-family: monospace; border-radius: 5px;'
'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';
}
},
refreshLogs: function(filter, 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

View File

@ -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');

View File

@ -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 + ')')
]);
},
renderSearchBox: function() {
var self = this;
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();
}
})
]);
},
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 {
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;
}
// Stop button
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;
}
// 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('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')
]));
}
// Stop button (only if running)
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')));
buttons.push(E('button', {
'class': 'sh-btn sh-btn-danger',
'click': L.bind(this.performAction, this, service.name, 'stop')
}, [
E('span', {}, '⏹️'),
E('span', {}, 'Stop')
]));
}
// 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')));
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) {
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')));
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 {
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')));
buttons.push(E('button', {
'class': 'sh-btn sh-btn-primary',
'click': L.bind(this.performAction, this, service.name, 'enable')
}, [
E('span', {}, '✓'),
E('span', {}, 'Enable')
]));
}
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)
]),
E('td', { 'class': 'td' }, enabledText),
E('td', { 'class': 'td' }, actionsDiv)
]));
}, this));
return buttons;
},
section.appendChild(table);
v.appendChild(section);
getStats: function() {
var stats = {
total: this.services.length,
running: 0,
stopped: 0,
enabled: 0,
disabled: 0
};
return v;
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) {