secubox-openwrt/luci-app-secubox-bonus/htdocs/luci-static/secubox/demo-traffic-shaper.html
CyberMind-FR 086e62584e feat: add luci-app-secubox-bonus package with website content
Created new independent package to integrate SecuBox marketing and
documentation website. Includes demo pages, tutorials, and multi-language
content previously deployed separately.

Package contents:
- 36 static files (HTML, JS, JSON)
- 16 module demo pages (auth, bandwidth, cdn-cache, client-guardian, etc.)
- 3 blog tutorials (setup guides)
- 13 language translations (en, fr, de, es, pt, it, nl, ru, ar, zh, ja, ko, hi)
- Campaign and landing pages

Files accessible at: /luci-static/secubox/
Main URL: http://router-ip/luci-static/secubox/index.html

Package info:
- Version: 0.1.0-1
- Size: ~500KB
- Dependencies: luci-base only
- No RPCD/backend components

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-31 12:12:55 +01:00

319 lines
18 KiB
HTML

<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Traffic Shaper - Démo | SecuBox</title>
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>🚦</text></svg>">
<style>
:root { --primary: #14b8a6; --primary-dark: #0d9488; --success: #22c55e; --warning: #f59e0b; --danger: #ef4444; --dark: #0f172a; --darker: #020617; --card: #1e293b; --text: #f1f5f9; --text-muted: #94a3b8; --border: #334155; }
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: system-ui, sans-serif; background: var(--darker); color: var(--text); min-height: 100vh; }
.header { background: linear-gradient(135deg, var(--primary-dark), var(--primary)); padding: 40px 24px; text-align: center; }
.header h1 { font-size: 36px; margin-bottom: 8px; }
.header p { opacity: 0.9; font-size: 18px; }
.back-link { position: absolute; top: 20px; left: 20px; color: white; text-decoration: none; opacity: 0.8; }
.container { max-width: 1400px; margin: 0 auto; padding: 32px 24px; }
.stats-row { display: grid; grid-template-columns: repeat(4, 1fr); gap: 16px; margin-bottom: 32px; }
.stat-card { background: var(--card); padding: 24px; border-radius: 12px; text-align: center; border: 1px solid var(--border); }
.stat-value { font-size: 28px; font-weight: 700; }
.stat-label { color: var(--text-muted); font-size: 13px; margin-top: 4px; }
.main-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 24px; }
.section { background: var(--card); border-radius: 16px; padding: 24px; border: 1px solid var(--border); margin-bottom: 24px; }
.section-title { font-size: 18px; font-weight: 600; margin-bottom: 20px; display: flex; align-items: center; gap: 8px; }
.shaper-config { display: grid; grid-template-columns: 1fr 1fr; gap: 24px; margin-bottom: 24px; }
.config-card { background: var(--dark); padding: 24px; border-radius: 12px; }
.config-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px; }
.config-title { font-weight: 600; font-size: 16px; }
.config-value { font-size: 24px; font-weight: 700; color: var(--primary); }
.config-slider { width: 100%; height: 8px; background: var(--border); border-radius: 4px; appearance: none; cursor: pointer; }
.config-slider::-webkit-slider-thumb { appearance: none; width: 20px; height: 20px; background: var(--primary); border-radius: 50%; cursor: pointer; }
.config-labels { display: flex; justify-content: space-between; font-size: 12px; color: var(--text-muted); margin-top: 8px; }
.qdisc-card { background: var(--dark); padding: 20px; border-radius: 12px; margin-bottom: 16px; display: flex; align-items: center; gap: 16px; cursor: pointer; border: 2px solid transparent; transition: all 0.2s; }
.qdisc-card:hover { border-color: var(--primary); }
.qdisc-card.selected { border-color: var(--success); background: rgba(34, 197, 94, 0.1); }
.qdisc-icon { font-size: 32px; }
.qdisc-info { flex: 1; }
.qdisc-name { font-weight: 700; font-size: 16px; margin-bottom: 4px; }
.qdisc-desc { font-size: 13px; color: var(--text-muted); }
.qdisc-check { width: 24px; height: 24px; border: 2px solid var(--border); border-radius: 50%; display: flex; align-items: center; justify-content: center; }
.qdisc-card.selected .qdisc-check { background: var(--success); border-color: var(--success); color: white; }
.latency-graph { background: var(--dark); padding: 24px; border-radius: 12px; }
.graph-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px; }
.graph-title { font-weight: 600; }
.graph-value { font-size: 24px; font-weight: 700; color: var(--success); }
.graph-container { height: 150px; display: flex; align-items: flex-end; gap: 4px; }
.graph-bar { flex: 1; background: linear-gradient(to top, var(--primary-dark), var(--primary)); border-radius: 2px 2px 0 0; transition: height 0.3s; }
.graph-labels { display: flex; justify-content: space-between; font-size: 11px; color: var(--text-muted); margin-top: 8px; }
.bufferbloat-meter { background: var(--dark); padding: 24px; border-radius: 12px; text-align: center; }
.meter-title { font-weight: 600; margin-bottom: 16px; }
.meter-gauge { width: 200px; height: 100px; margin: 0 auto; position: relative; overflow: hidden; }
.meter-arc { position: absolute; width: 200px; height: 200px; border: 20px solid var(--border); border-radius: 50%; border-top-color: transparent; border-right-color: transparent; transform: rotate(45deg); }
.meter-fill { position: absolute; width: 200px; height: 200px; border: 20px solid transparent; border-radius: 50%; border-bottom-color: var(--success); border-left-color: var(--success); transform: rotate(45deg); clip-path: polygon(0 50%, 100% 50%, 100% 100%, 0 100%); }
.meter-needle { position: absolute; bottom: 0; left: 50%; width: 4px; height: 80px; background: var(--danger); transform-origin: bottom center; transform: translateX(-50%) rotate(-45deg); border-radius: 2px; transition: transform 0.5s; }
.meter-value { font-size: 32px; font-weight: 700; color: var(--success); margin-top: 16px; }
.meter-label { color: var(--text-muted); font-size: 14px; }
.rules-table { width: 100%; }
.rule-row { display: grid; grid-template-columns: 1fr 100px 100px 100px 80px; gap: 12px; padding: 14px; background: var(--dark); border-radius: 10px; margin-bottom: 10px; align-items: center; }
.rule-row.header { background: transparent; font-weight: 600; color: var(--text-muted); font-size: 12px; text-transform: uppercase; }
.rule-name { font-weight: 600; display: flex; align-items: center; gap: 8px; }
.rule-icon { font-size: 18px; }
.rule-priority { padding: 4px 10px; border-radius: 6px; font-size: 12px; font-weight: 600; }
.priority-high { background: rgba(239, 68, 68, 0.2); color: var(--danger); }
.priority-medium { background: rgba(245, 158, 11, 0.2); color: var(--warning); }
.priority-low { background: rgba(100, 116, 139, 0.2); color: var(--text-muted); }
.toggle { position: relative; width: 44px; height: 24px; background: var(--border); border-radius: 12px; cursor: pointer; transition: all 0.2s; }
.toggle.active { background: var(--success); }
.toggle::after { content: ''; position: absolute; top: 2px; left: 2px; width: 20px; height: 20px; background: white; border-radius: 50%; transition: all 0.2s; }
.toggle.active::after { left: 22px; }
@media (max-width: 1024px) { .main-grid { grid-template-columns: 1fr; } .stats-row { grid-template-columns: repeat(2, 1fr); } .shaper-config { grid-template-columns: 1fr; } }
@media (max-width: 768px) { .rule-row { grid-template-columns: 1fr 80px 60px; } }
</style>
</head>
<body>
<div class="header">
<a href="index.html" class="back-link" data-i18n="demo.backToMain">← Retour à l'accueil</a>
<h1>🚦 Traffic Shaper</h1>
<p>Contrôle avancé du trafic avec CAKE</p>
</div>
<div class="container">
<div class="stats-row">
<div class="stat-card">
<div class="stat-value" style="color: var(--success);"></div>
<div class="stat-label">CAKE actif</div>
</div>
<div class="stat-card">
<div class="stat-value" style="color: var(--primary);" id="latency">8 ms</div>
<div class="stat-label">Latence moyenne</div>
</div>
<div class="stat-card">
<div class="stat-value" style="color: var(--success);" id="jitter">2 ms</div>
<div class="stat-label">Jitter</div>
</div>
<div class="stat-card">
<div class="stat-value" style="color: var(--success);">A+</div>
<div class="stat-label">Bufferbloat Grade</div>
</div>
</div>
<div class="shaper-config">
<div class="config-card">
<div class="config-header">
<span class="config-title">📥 Download Limit</span>
<span class="config-value" id="dl-val">95 Mbps</span>
</div>
<input type="range" class="config-slider" id="dl-slider" min="10" max="100" value="95">
<div class="config-labels">
<span>10 Mbps</span>
<span>100 Mbps</span>
</div>
</div>
<div class="config-card">
<div class="config-header">
<span class="config-title">📤 Upload Limit</span>
<span class="config-value" id="ul-val">20 Mbps</span>
</div>
<input type="range" class="config-slider" id="ul-slider" min="5" max="50" value="20">
<div class="config-labels">
<span>5 Mbps</span>
<span>50 Mbps</span>
</div>
</div>
</div>
<div class="main-grid">
<div class="section">
<div class="section-title">⚙️ Algorithme de queue</div>
<div class="qdisc-card selected">
<div class="qdisc-icon">🍰</div>
<div class="qdisc-info">
<div class="qdisc-name">CAKE</div>
<div class="qdisc-desc">Common Applications Kept Enhanced - Meilleur pour la plupart des cas</div>
</div>
<div class="qdisc-check"></div>
</div>
<div class="qdisc-card">
<div class="qdisc-icon">📊</div>
<div class="qdisc-info">
<div class="qdisc-name">fq_codel</div>
<div class="qdisc-desc">Fair Queue CoDel - Bon compromis performance/simplicité</div>
</div>
<div class="qdisc-check"></div>
</div>
<div class="qdisc-card">
<div class="qdisc-icon">🎯</div>
<div class="qdisc-info">
<div class="qdisc-name">SQM (Simple)</div>
<div class="qdisc-desc">Configuration simplifiée pour débutants</div>
</div>
<div class="qdisc-check"></div>
</div>
<div class="qdisc-card">
<div class="qdisc-icon">🔧</div>
<div class="qdisc-info">
<div class="qdisc-name">HTB + fq_codel</div>
<div class="qdisc-desc">Contrôle avancé avec classes hierarchiques</div>
</div>
<div class="qdisc-check"></div>
</div>
</div>
<div>
<div class="latency-graph">
<div class="graph-header">
<span class="graph-title">📈 Latence (dernières 60s)</span>
<span class="graph-value" id="current-latency">8 ms</span>
</div>
<div class="graph-container" id="latency-bars"></div>
<div class="graph-labels">
<span>-60s</span>
<span>-30s</span>
<span>Now</span>
</div>
</div>
<div class="bufferbloat-meter" style="margin-top: 24px;">
<div class="meter-title">🎯 Score Bufferbloat</div>
<div class="meter-gauge">
<div class="meter-arc"></div>
<div class="meter-fill"></div>
<div class="meter-needle" id="meter-needle"></div>
</div>
<div class="meter-value">A+</div>
<div class="meter-label">Excellent - Pas de bufferbloat détecté</div>
</div>
</div>
</div>
<div class="section">
<div class="section-title">📋 Règles de priorité</div>
<div class="rule-row header">
<div>Type de trafic</div>
<div>Priorité</div>
<div>DSCP</div>
<div>Bandwidth</div>
<div>Actif</div>
</div>
<div class="rule-row">
<div class="rule-name"><span class="rule-icon">📞</span>VoIP / SIP</div>
<div><span class="rule-priority priority-high">Temps réel</span></div>
<div style="color: var(--text-muted);">EF (46)</div>
<div style="color: var(--primary);">30%</div>
<div><div class="toggle active"></div></div>
</div>
<div class="rule-row">
<div class="rule-name"><span class="rule-icon">🎮</span>Gaming</div>
<div><span class="rule-priority priority-high">Interactif</span></div>
<div style="color: var(--text-muted);">CS4 (32)</div>
<div style="color: var(--primary);">20%</div>
<div><div class="toggle active"></div></div>
</div>
<div class="rule-row">
<div class="rule-name"><span class="rule-icon">📹</span>Visio</div>
<div><span class="rule-priority priority-high">Temps réel</span></div>
<div style="color: var(--text-muted);">AF41 (34)</div>
<div style="color: var(--primary);">25%</div>
<div><div class="toggle active"></div></div>
</div>
<div class="rule-row">
<div class="rule-name"><span class="rule-icon">📺</span>Streaming</div>
<div><span class="rule-priority priority-medium">Standard</span></div>
<div style="color: var(--text-muted);">AF31 (26)</div>
<div style="color: var(--primary);"></div>
<div><div class="toggle active"></div></div>
</div>
<div class="rule-row">
<div class="rule-name"><span class="rule-icon">🌐</span>Web browsing</div>
<div><span class="rule-priority priority-medium">Standard</span></div>
<div style="color: var(--text-muted);">CS0 (0)</div>
<div style="color: var(--primary);"></div>
<div><div class="toggle active"></div></div>
</div>
<div class="rule-row">
<div class="rule-name"><span class="rule-icon">📥</span>Downloads / P2P</div>
<div><span class="rule-priority priority-low">Bulk</span></div>
<div style="color: var(--text-muted);">CS1 (8)</div>
<div style="color: var(--primary);">5%</div>
<div><div class="toggle active"></div></div>
</div>
</div>
</div>
<script>
// Initialize latency graph
const container = document.getElementById('latency-bars');
for (let i = 0; i < 60; i++) {
const bar = document.createElement('div');
bar.className = 'graph-bar';
bar.style.height = (20 + Math.random() * 30) + '%';
container.appendChild(bar);
}
// Sliders
document.getElementById('dl-slider').addEventListener('input', (e) => {
document.getElementById('dl-val').textContent = e.target.value + ' Mbps';
});
document.getElementById('ul-slider').addEventListener('input', (e) => {
document.getElementById('ul-val').textContent = e.target.value + ' Mbps';
});
// Toggles
document.querySelectorAll('.toggle').forEach(toggle => {
toggle.addEventListener('click', () => toggle.classList.toggle('active'));
});
// Qdisc selection
document.querySelectorAll('.qdisc-card').forEach(card => {
card.addEventListener('click', () => {
document.querySelectorAll('.qdisc-card').forEach(c => c.classList.remove('selected'));
card.classList.add('selected');
});
});
// Update stats
setInterval(() => {
const latency = 5 + Math.random() * 8;
const jitter = 1 + Math.random() * 3;
document.getElementById('latency').textContent = latency.toFixed(0) + ' ms';
document.getElementById('jitter').textContent = jitter.toFixed(0) + ' ms';
document.getElementById('current-latency').textContent = latency.toFixed(0) + ' ms';
// Update graph
const bars = container.children;
for (let bar of bars) {
bar.style.height = (15 + Math.random() * 40) + '%';
}
// Update needle (good = -45deg, bad = +45deg)
const score = -45 + Math.random() * 20;
document.getElementById('meter-needle').style.transform = `translateX(-50%) rotate(${score}deg)`;
}, 2000);
</script>
<!-- Multi-language System -->
<script src="/i18n.js"></script>
</body>
</html>