Split style and js code in anothers files
This commit is contained in:
parent
8c8986e8dc
commit
a044bb759e
3 changed files with 594 additions and 596 deletions
247
static/app.js
Normal file
247
static/app.js
Normal file
|
@ -0,0 +1,247 @@
|
||||||
|
// État global de l'application
|
||||||
|
let appState = {
|
||||||
|
selectedWifi: null,
|
||||||
|
hotspotEnabled: true,
|
||||||
|
connectedDevices: [],
|
||||||
|
wifiNetworks: [],
|
||||||
|
uptime: 0,
|
||||||
|
dataUsage: 0
|
||||||
|
};
|
||||||
|
|
||||||
|
// Simulation de données
|
||||||
|
const mockWifiNetworks = [
|
||||||
|
{ ssid: "Hotel_WiFi", signal: 4, security: "WPA2", channel: 6 },
|
||||||
|
{ ssid: "Starbucks_Free", signal: 3, security: "Open", channel: 11 },
|
||||||
|
{ ssid: "AndroidAP", signal: 2, security: "WPA2", channel: 1 },
|
||||||
|
{ ssid: "iPhone_Hotspot", signal: 5, security: "WPA2", channel: 6 },
|
||||||
|
{ ssid: "Guest_Network", signal: 1, security: "WPA", channel: 11 }
|
||||||
|
];
|
||||||
|
|
||||||
|
const mockDevices = [
|
||||||
|
{ name: "iPhone 13", type: "mobile", mac: "AA:BB:CC:DD:EE:FF", ip: "192.168.1.101" },
|
||||||
|
{ name: "MacBook Pro", type: "laptop", mac: "11:22:33:44:55:66", ip: "192.168.1.102" },
|
||||||
|
{ name: "iPad", type: "tablet", mac: "77:88:99:AA:BB:CC", ip: "192.168.1.103" }
|
||||||
|
];
|
||||||
|
|
||||||
|
// Initialisation
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
initializeApp();
|
||||||
|
startPeriodicUpdates();
|
||||||
|
});
|
||||||
|
|
||||||
|
function initializeApp() {
|
||||||
|
updateWifiList();
|
||||||
|
updateDevicesList();
|
||||||
|
updateStats();
|
||||||
|
addLog("Système", "Interface web initialisée");
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateWifiList() {
|
||||||
|
const wifiList = document.getElementById('wifiList');
|
||||||
|
wifiList.innerHTML = '';
|
||||||
|
|
||||||
|
mockWifiNetworks.forEach((network, index) => {
|
||||||
|
const wifiItem = document.createElement('div');
|
||||||
|
wifiItem.className = 'wifi-item';
|
||||||
|
wifiItem.onclick = () => selectWifi(network, wifiItem);
|
||||||
|
|
||||||
|
wifiItem.innerHTML = `
|
||||||
|
<div>
|
||||||
|
<strong>${network.ssid}</strong>
|
||||||
|
<div style="font-size: 0.8em; color: #666;">${network.security} • Canal ${network.channel}</div>
|
||||||
|
</div>
|
||||||
|
<div class="wifi-signal">
|
||||||
|
${generateSignalBars(network.signal)}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
wifiList.appendChild(wifiItem);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateSignalBars(strength) {
|
||||||
|
const bars = [];
|
||||||
|
for (let i = 1; i <= 5; i++) {
|
||||||
|
const height = i * 3;
|
||||||
|
const active = i <= strength ? 'active' : '';
|
||||||
|
bars.push(`<div class="signal-bar ${active}" style="height: ${height}px;"></div>`);
|
||||||
|
}
|
||||||
|
return `<div class="signal-bars">${bars.join('')}</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectWifi(network, element) {
|
||||||
|
// Retirer la sélection précédente
|
||||||
|
document.querySelectorAll('.wifi-item').forEach(item => {
|
||||||
|
item.classList.remove('selected');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Ajouter la sélection
|
||||||
|
element.classList.add('selected');
|
||||||
|
appState.selectedWifi = network;
|
||||||
|
|
||||||
|
addLog("WiFi", `Réseau sélectionné: ${network.ssid}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateDevicesList() {
|
||||||
|
const devicesList = document.getElementById('devicesList');
|
||||||
|
devicesList.innerHTML = '';
|
||||||
|
|
||||||
|
mockDevices.forEach(device => {
|
||||||
|
const deviceCard = document.createElement('div');
|
||||||
|
deviceCard.className = 'device-card';
|
||||||
|
|
||||||
|
const deviceIcon = getDeviceIcon(device.type);
|
||||||
|
|
||||||
|
deviceCard.innerHTML = `
|
||||||
|
${deviceIcon}
|
||||||
|
<div style="font-weight: 500;">${device.name}</div>
|
||||||
|
<div style="font-size: 0.8em; color: #666;">${device.ip}</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
devicesList.appendChild(deviceCard);
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('connectedDevices').textContent = mockDevices.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDeviceIcon(type) {
|
||||||
|
const icons = {
|
||||||
|
mobile: '<svg class="device-icon" viewBox="0 0 24 24"><path d="M17 1H7c-1.1 0-2 .9-2 2v18c0 1.1.9 2 2 2h10c1.1 0 2-.9 2-2V3c0-1.1-.9-2-2-2zm0 18H7V5h10v14z"/></svg>',
|
||||||
|
laptop: '<svg class="device-icon" viewBox="0 0 24 24"><path d="M20 18c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2H4c-1.1 0-2 .9-2 2v10c0 1.1.9 2 2 2H0v2h24v-2h-4zM4 6h16v10H4V6z"/></svg>',
|
||||||
|
tablet: '<svg class="device-icon" viewBox="0 0 24 24"><path d="M21 4H3c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h18c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm0 14H3V6h18v12z"/></svg>'
|
||||||
|
};
|
||||||
|
return icons[type] || icons.mobile;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateStats() {
|
||||||
|
appState.uptime += 1;
|
||||||
|
appState.dataUsage += Math.random() * 0.5;
|
||||||
|
|
||||||
|
const hours = Math.floor(appState.uptime / 3600);
|
||||||
|
const minutes = Math.floor((appState.uptime % 3600) / 60);
|
||||||
|
const seconds = appState.uptime % 60;
|
||||||
|
|
||||||
|
document.getElementById('uptime').textContent =
|
||||||
|
`${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
|
||||||
|
|
||||||
|
document.getElementById('dataUsage').textContent = `${appState.dataUsage.toFixed(1)} MB`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function addLog(source, message) {
|
||||||
|
const logContainer = document.getElementById('logContainer');
|
||||||
|
const timestamp = new Date().toLocaleTimeString();
|
||||||
|
const logEntry = document.createElement('div');
|
||||||
|
logEntry.className = 'log-entry';
|
||||||
|
logEntry.innerHTML = `<span class="log-timestamp">[${timestamp}]</span> ${source}: ${message}`;
|
||||||
|
|
||||||
|
logContainer.appendChild(logEntry);
|
||||||
|
logContainer.scrollTop = logContainer.scrollHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
function showNotification(message, type = 'success') {
|
||||||
|
const notification = document.getElementById('notification');
|
||||||
|
notification.textContent = message;
|
||||||
|
notification.className = `notification ${type}`;
|
||||||
|
notification.classList.add('show');
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
notification.classList.remove('show');
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fonctions d'action
|
||||||
|
function scanWifi() {
|
||||||
|
const scanBtn = document.getElementById('scanBtn');
|
||||||
|
const originalText = scanBtn.textContent;
|
||||||
|
|
||||||
|
scanBtn.innerHTML = '<div class="loading"></div> Scan en cours...';
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
updateWifiList();
|
||||||
|
scanBtn.textContent = originalText;
|
||||||
|
showNotification('Scan terminé - Réseaux mis à jour');
|
||||||
|
addLog("WiFi", "Scan des réseaux terminé");
|
||||||
|
}, 2000);
|
||||||
|
}
|
||||||
|
|
||||||
|
function connectToWifi() {
|
||||||
|
if (!appState.selectedWifi) {
|
||||||
|
showNotification('Veuillez sélectionner un réseau WiFi', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const password = document.getElementById('wifiPassword').value;
|
||||||
|
if (!password && appState.selectedWifi.security !== 'Open') {
|
||||||
|
showNotification('Mot de passe requis', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const connectBtn = document.getElementById('connectBtn');
|
||||||
|
const originalText = connectBtn.textContent;
|
||||||
|
|
||||||
|
connectBtn.innerHTML = '<div class="loading"></div> Connexion...';
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
connectBtn.textContent = originalText;
|
||||||
|
showNotification(`Connecté à ${appState.selectedWifi.ssid}`);
|
||||||
|
addLog("WiFi", `Connexion établie avec ${appState.selectedWifi.ssid}`);
|
||||||
|
|
||||||
|
// Mettre à jour le statut
|
||||||
|
document.getElementById('connectionStatus').innerHTML = `
|
||||||
|
<div class="status-dot"></div>
|
||||||
|
<span>Connecté à ${appState.selectedWifi.ssid}</span>
|
||||||
|
`;
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateHotspot() {
|
||||||
|
const name = document.getElementById('hotspotName').value;
|
||||||
|
const password = document.getElementById('hotspotPassword').value;
|
||||||
|
const channel = document.getElementById('hotspotChannel').value;
|
||||||
|
|
||||||
|
if (!name || !password) {
|
||||||
|
showNotification('Nom et mot de passe requis', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
showNotification('Configuration du hotspot mise à jour');
|
||||||
|
addLog("Hotspot", `Configuration mise à jour: ${name} (Canal ${channel})`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleHotspot() {
|
||||||
|
appState.hotspotEnabled = !appState.hotspotEnabled;
|
||||||
|
const btn = document.getElementById('hotspotBtn');
|
||||||
|
|
||||||
|
if (appState.hotspotEnabled) {
|
||||||
|
btn.textContent = 'Arrêter le hotspot';
|
||||||
|
showNotification('Hotspot activé');
|
||||||
|
addLog("Hotspot", "Hotspot activé");
|
||||||
|
} else {
|
||||||
|
btn.textContent = 'Démarrer le hotspot';
|
||||||
|
showNotification('Hotspot désactivé');
|
||||||
|
addLog("Hotspot", "Hotspot désactivé");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearLogs() {
|
||||||
|
document.getElementById('logContainer').innerHTML = '';
|
||||||
|
addLog("Système", "Logs effacés");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mises à jour périodiques
|
||||||
|
function startPeriodicUpdates() {
|
||||||
|
setInterval(updateStats, 1000);
|
||||||
|
setInterval(() => {
|
||||||
|
// Simulation de nouveaux logs
|
||||||
|
if (Math.random() > 0.95) {
|
||||||
|
const events = [
|
||||||
|
"Nouveau client connecté",
|
||||||
|
"Paquet routé vers l'extérieur",
|
||||||
|
"Vérification de la connexion",
|
||||||
|
"Mise à jour des tables de routage"
|
||||||
|
];
|
||||||
|
const randomEvent = events[Math.floor(Math.random() * events.length)];
|
||||||
|
addLog("Système", randomEvent);
|
||||||
|
}
|
||||||
|
}, 5000);
|
||||||
|
}
|
|
@ -4,346 +4,7 @@
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>WiFi Repeater Control</title>
|
<title>WiFi Repeater Control</title>
|
||||||
<style>
|
<link rel="stylesheet" href="/style.css">
|
||||||
* {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
||||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
||||||
min-height: 100vh;
|
|
||||||
padding: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.container {
|
|
||||||
max-width: 1200px;
|
|
||||||
margin: 0 auto;
|
|
||||||
background: rgba(255, 255, 255, 0.95);
|
|
||||||
backdrop-filter: blur(10px);
|
|
||||||
border-radius: 20px;
|
|
||||||
padding: 30px;
|
|
||||||
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.header {
|
|
||||||
text-align: center;
|
|
||||||
margin-bottom: 40px;
|
|
||||||
padding-bottom: 20px;
|
|
||||||
border-bottom: 2px solid #e0e0e0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header h1 {
|
|
||||||
color: #333;
|
|
||||||
font-size: 2.5em;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-indicator {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 10px;
|
|
||||||
padding: 10px 20px;
|
|
||||||
border-radius: 25px;
|
|
||||||
font-weight: 500;
|
|
||||||
margin-top: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-online {
|
|
||||||
background: #d4edda;
|
|
||||||
color: #155724;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-offline {
|
|
||||||
background: #f8d7da;
|
|
||||||
color: #721c24;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-dot {
|
|
||||||
width: 12px;
|
|
||||||
height: 12px;
|
|
||||||
border-radius: 50%;
|
|
||||||
background: currentColor;
|
|
||||||
animation: pulse 2s infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes pulse {
|
|
||||||
0%, 100% { opacity: 1; }
|
|
||||||
50% { opacity: 0.5; }
|
|
||||||
}
|
|
||||||
|
|
||||||
.grid {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
|
|
||||||
gap: 30px;
|
|
||||||
margin-bottom: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card {
|
|
||||||
background: white;
|
|
||||||
border-radius: 15px;
|
|
||||||
padding: 25px;
|
|
||||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
|
|
||||||
transition: transform 0.3s ease, box-shadow 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card:hover {
|
|
||||||
transform: translateY(-5px);
|
|
||||||
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.15);
|
|
||||||
}
|
|
||||||
|
|
||||||
.card h2 {
|
|
||||||
color: #333;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
font-size: 1.4em;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon {
|
|
||||||
width: 24px;
|
|
||||||
height: 24px;
|
|
||||||
fill: currentColor;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-group {
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-group label {
|
|
||||||
display: block;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
color: #555;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-group input, .form-group select {
|
|
||||||
width: 100%;
|
|
||||||
padding: 12px 16px;
|
|
||||||
border: 2px solid #e0e0e0;
|
|
||||||
border-radius: 8px;
|
|
||||||
font-size: 16px;
|
|
||||||
transition: border-color 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-group input:focus, .form-group select:focus {
|
|
||||||
outline: none;
|
|
||||||
border-color: #667eea;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn {
|
|
||||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
||||||
color: white;
|
|
||||||
border: none;
|
|
||||||
padding: 12px 24px;
|
|
||||||
border-radius: 8px;
|
|
||||||
font-size: 16px;
|
|
||||||
font-weight: 500;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn:hover {
|
|
||||||
transform: translateY(-2px);
|
|
||||||
box-shadow: 0 10px 20px rgba(102, 126, 234, 0.3);
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn:active {
|
|
||||||
transform: translateY(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-secondary {
|
|
||||||
background: #6c757d;
|
|
||||||
margin-top: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-danger {
|
|
||||||
background: #dc3545;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wifi-list {
|
|
||||||
max-height: 300px;
|
|
||||||
overflow-y: auto;
|
|
||||||
border: 1px solid #e0e0e0;
|
|
||||||
border-radius: 8px;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wifi-item {
|
|
||||||
padding: 12px 16px;
|
|
||||||
border-bottom: 1px solid #f0f0f0;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: background-color 0.2s ease;
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wifi-item:hover {
|
|
||||||
background-color: #f8f9fa;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wifi-item:last-child {
|
|
||||||
border-bottom: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wifi-item.selected {
|
|
||||||
background-color: #e7f3ff;
|
|
||||||
border-left: 4px solid #667eea;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wifi-signal {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.signal-strength {
|
|
||||||
width: 20px;
|
|
||||||
height: 20px;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.signal-bars {
|
|
||||||
display: flex;
|
|
||||||
gap: 2px;
|
|
||||||
align-items: flex-end;
|
|
||||||
}
|
|
||||||
|
|
||||||
.signal-bar {
|
|
||||||
width: 3px;
|
|
||||||
background: #ccc;
|
|
||||||
border-radius: 1px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.signal-bar.active {
|
|
||||||
background: #28a745;
|
|
||||||
}
|
|
||||||
|
|
||||||
.devices-grid {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
||||||
gap: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.device-card {
|
|
||||||
background: #f8f9fa;
|
|
||||||
border-radius: 10px;
|
|
||||||
padding: 15px;
|
|
||||||
text-align: center;
|
|
||||||
transition: transform 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.device-card:hover {
|
|
||||||
transform: scale(1.05);
|
|
||||||
}
|
|
||||||
|
|
||||||
.device-icon {
|
|
||||||
width: 40px;
|
|
||||||
height: 40px;
|
|
||||||
margin: 0 auto 10px;
|
|
||||||
fill: #667eea;
|
|
||||||
}
|
|
||||||
|
|
||||||
.log-container {
|
|
||||||
background: #1a1a1a;
|
|
||||||
color: #00ff00;
|
|
||||||
padding: 20px;
|
|
||||||
border-radius: 10px;
|
|
||||||
font-family: 'Courier New', monospace;
|
|
||||||
font-size: 14px;
|
|
||||||
max-height: 300px;
|
|
||||||
overflow-y: auto;
|
|
||||||
line-height: 1.4;
|
|
||||||
}
|
|
||||||
|
|
||||||
.log-entry {
|
|
||||||
margin-bottom: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.log-timestamp {
|
|
||||||
color: #888;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stats-grid {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
|
||||||
gap: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stat-card {
|
|
||||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
||||||
color: white;
|
|
||||||
padding: 20px;
|
|
||||||
border-radius: 10px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stat-value {
|
|
||||||
font-size: 2em;
|
|
||||||
font-weight: bold;
|
|
||||||
margin-bottom: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stat-label {
|
|
||||||
font-size: 0.9em;
|
|
||||||
opacity: 0.9;
|
|
||||||
}
|
|
||||||
|
|
||||||
.loading {
|
|
||||||
display: inline-block;
|
|
||||||
width: 20px;
|
|
||||||
height: 20px;
|
|
||||||
border: 3px solid #f3f3f3;
|
|
||||||
border-top: 3px solid #667eea;
|
|
||||||
border-radius: 50%;
|
|
||||||
animation: spin 1s linear infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes spin {
|
|
||||||
0% { transform: rotate(0deg); }
|
|
||||||
100% { transform: rotate(360deg); }
|
|
||||||
}
|
|
||||||
|
|
||||||
.notification {
|
|
||||||
position: fixed;
|
|
||||||
top: 20px;
|
|
||||||
right: 20px;
|
|
||||||
padding: 15px 20px;
|
|
||||||
border-radius: 10px;
|
|
||||||
color: white;
|
|
||||||
font-weight: 500;
|
|
||||||
z-index: 1000;
|
|
||||||
transform: translateX(100%);
|
|
||||||
transition: transform 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.notification.show {
|
|
||||||
transform: translateX(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
.notification.success {
|
|
||||||
background: #28a745;
|
|
||||||
}
|
|
||||||
|
|
||||||
.notification.error {
|
|
||||||
background: #dc3545;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
.grid {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
|
|
||||||
.container {
|
|
||||||
padding: 20px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
|
@ -378,7 +39,7 @@
|
||||||
</svg>
|
</svg>
|
||||||
Connexion WiFi Externe
|
Connexion WiFi Externe
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>Réseaux disponibles</label>
|
<label>Réseaux disponibles</label>
|
||||||
<div class="wifi-list" id="wifiList">
|
<div class="wifi-list" id="wifiList">
|
||||||
|
@ -394,7 +55,7 @@
|
||||||
<button class="btn" onclick="connectToWifi()">
|
<button class="btn" onclick="connectToWifi()">
|
||||||
<span id="connectBtn">Se connecter</span>
|
<span id="connectBtn">Se connecter</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button class="btn btn-secondary" onclick="scanWifi()">
|
<button class="btn btn-secondary" onclick="scanWifi()">
|
||||||
<span id="scanBtn">Scanner les réseaux</span>
|
<span id="scanBtn">Scanner les réseaux</span>
|
||||||
</button>
|
</button>
|
||||||
|
@ -407,7 +68,7 @@
|
||||||
</svg>
|
</svg>
|
||||||
Configuration Hotspot
|
Configuration Hotspot
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="hotspotName">Nom du réseau (SSID)</label>
|
<label for="hotspotName">Nom du réseau (SSID)</label>
|
||||||
<input type="text" id="hotspotName" value="MyRepeater" placeholder="Nom du hotspot">
|
<input type="text" id="hotspotName" value="MyRepeater" placeholder="Nom du hotspot">
|
||||||
|
@ -442,7 +103,7 @@
|
||||||
</svg>
|
</svg>
|
||||||
Appareils connectés
|
Appareils connectés
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<div class="devices-grid" id="devicesList">
|
<div class="devices-grid" id="devicesList">
|
||||||
<!-- Liste des appareils sera remplie par JavaScript -->
|
<!-- Liste des appareils sera remplie par JavaScript -->
|
||||||
</div>
|
</div>
|
||||||
|
@ -455,11 +116,11 @@
|
||||||
</svg>
|
</svg>
|
||||||
Logs système
|
Logs système
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<div class="log-container" id="logContainer">
|
<div class="log-container" id="logContainer">
|
||||||
<!-- Les logs seront ajoutés ici -->
|
<!-- Les logs seront ajoutés ici -->
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button class="btn btn-secondary" onclick="clearLogs()" style="margin-top: 15px;">
|
<button class="btn btn-secondary" onclick="clearLogs()" style="margin-top: 15px;">
|
||||||
Effacer les logs
|
Effacer les logs
|
||||||
</button>
|
</button>
|
||||||
|
@ -469,254 +130,6 @@
|
||||||
|
|
||||||
<div class="notification" id="notification"></div>
|
<div class="notification" id="notification"></div>
|
||||||
|
|
||||||
<script>
|
<script src="/app.js"></script>
|
||||||
// État global de l'application
|
|
||||||
let appState = {
|
|
||||||
selectedWifi: null,
|
|
||||||
hotspotEnabled: true,
|
|
||||||
connectedDevices: [],
|
|
||||||
wifiNetworks: [],
|
|
||||||
uptime: 0,
|
|
||||||
dataUsage: 0
|
|
||||||
};
|
|
||||||
|
|
||||||
// Simulation de données
|
|
||||||
const mockWifiNetworks = [
|
|
||||||
{ ssid: "Hotel_WiFi", signal: 4, security: "WPA2", channel: 6 },
|
|
||||||
{ ssid: "Starbucks_Free", signal: 3, security: "Open", channel: 11 },
|
|
||||||
{ ssid: "AndroidAP", signal: 2, security: "WPA2", channel: 1 },
|
|
||||||
{ ssid: "iPhone_Hotspot", signal: 5, security: "WPA2", channel: 6 },
|
|
||||||
{ ssid: "Guest_Network", signal: 1, security: "WPA", channel: 11 }
|
|
||||||
];
|
|
||||||
|
|
||||||
const mockDevices = [
|
|
||||||
{ name: "iPhone 13", type: "mobile", mac: "AA:BB:CC:DD:EE:FF", ip: "192.168.1.101" },
|
|
||||||
{ name: "MacBook Pro", type: "laptop", mac: "11:22:33:44:55:66", ip: "192.168.1.102" },
|
|
||||||
{ name: "iPad", type: "tablet", mac: "77:88:99:AA:BB:CC", ip: "192.168.1.103" }
|
|
||||||
];
|
|
||||||
|
|
||||||
// Initialisation
|
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
|
||||||
initializeApp();
|
|
||||||
startPeriodicUpdates();
|
|
||||||
});
|
|
||||||
|
|
||||||
function initializeApp() {
|
|
||||||
updateWifiList();
|
|
||||||
updateDevicesList();
|
|
||||||
updateStats();
|
|
||||||
addLog("Système", "Interface web initialisée");
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateWifiList() {
|
|
||||||
const wifiList = document.getElementById('wifiList');
|
|
||||||
wifiList.innerHTML = '';
|
|
||||||
|
|
||||||
mockWifiNetworks.forEach((network, index) => {
|
|
||||||
const wifiItem = document.createElement('div');
|
|
||||||
wifiItem.className = 'wifi-item';
|
|
||||||
wifiItem.onclick = () => selectWifi(network, wifiItem);
|
|
||||||
|
|
||||||
wifiItem.innerHTML = `
|
|
||||||
<div>
|
|
||||||
<strong>${network.ssid}</strong>
|
|
||||||
<div style="font-size: 0.8em; color: #666;">${network.security} • Canal ${network.channel}</div>
|
|
||||||
</div>
|
|
||||||
<div class="wifi-signal">
|
|
||||||
${generateSignalBars(network.signal)}
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
wifiList.appendChild(wifiItem);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function generateSignalBars(strength) {
|
|
||||||
const bars = [];
|
|
||||||
for (let i = 1; i <= 5; i++) {
|
|
||||||
const height = i * 3;
|
|
||||||
const active = i <= strength ? 'active' : '';
|
|
||||||
bars.push(`<div class="signal-bar ${active}" style="height: ${height}px;"></div>`);
|
|
||||||
}
|
|
||||||
return `<div class="signal-bars">${bars.join('')}</div>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function selectWifi(network, element) {
|
|
||||||
// Retirer la sélection précédente
|
|
||||||
document.querySelectorAll('.wifi-item').forEach(item => {
|
|
||||||
item.classList.remove('selected');
|
|
||||||
});
|
|
||||||
|
|
||||||
// Ajouter la sélection
|
|
||||||
element.classList.add('selected');
|
|
||||||
appState.selectedWifi = network;
|
|
||||||
|
|
||||||
addLog("WiFi", `Réseau sélectionné: ${network.ssid}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateDevicesList() {
|
|
||||||
const devicesList = document.getElementById('devicesList');
|
|
||||||
devicesList.innerHTML = '';
|
|
||||||
|
|
||||||
mockDevices.forEach(device => {
|
|
||||||
const deviceCard = document.createElement('div');
|
|
||||||
deviceCard.className = 'device-card';
|
|
||||||
|
|
||||||
const deviceIcon = getDeviceIcon(device.type);
|
|
||||||
|
|
||||||
deviceCard.innerHTML = `
|
|
||||||
${deviceIcon}
|
|
||||||
<div style="font-weight: 500;">${device.name}</div>
|
|
||||||
<div style="font-size: 0.8em; color: #666;">${device.ip}</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
devicesList.appendChild(deviceCard);
|
|
||||||
});
|
|
||||||
|
|
||||||
document.getElementById('connectedDevices').textContent = mockDevices.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getDeviceIcon(type) {
|
|
||||||
const icons = {
|
|
||||||
mobile: '<svg class="device-icon" viewBox="0 0 24 24"><path d="M17 1H7c-1.1 0-2 .9-2 2v18c0 1.1.9 2 2 2h10c1.1 0 2-.9 2-2V3c0-1.1-.9-2-2-2zm0 18H7V5h10v14z"/></svg>',
|
|
||||||
laptop: '<svg class="device-icon" viewBox="0 0 24 24"><path d="M20 18c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2H4c-1.1 0-2 .9-2 2v10c0 1.1.9 2 2 2H0v2h24v-2h-4zM4 6h16v10H4V6z"/></svg>',
|
|
||||||
tablet: '<svg class="device-icon" viewBox="0 0 24 24"><path d="M21 4H3c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h18c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm0 14H3V6h18v12z"/></svg>'
|
|
||||||
};
|
|
||||||
return icons[type] || icons.mobile;
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateStats() {
|
|
||||||
appState.uptime += 1;
|
|
||||||
appState.dataUsage += Math.random() * 0.5;
|
|
||||||
|
|
||||||
const hours = Math.floor(appState.uptime / 3600);
|
|
||||||
const minutes = Math.floor((appState.uptime % 3600) / 60);
|
|
||||||
const seconds = appState.uptime % 60;
|
|
||||||
|
|
||||||
document.getElementById('uptime').textContent =
|
|
||||||
`${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
|
|
||||||
|
|
||||||
document.getElementById('dataUsage').textContent = `${appState.dataUsage.toFixed(1)} MB`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function addLog(source, message) {
|
|
||||||
const logContainer = document.getElementById('logContainer');
|
|
||||||
const timestamp = new Date().toLocaleTimeString();
|
|
||||||
const logEntry = document.createElement('div');
|
|
||||||
logEntry.className = 'log-entry';
|
|
||||||
logEntry.innerHTML = `<span class="log-timestamp">[${timestamp}]</span> ${source}: ${message}`;
|
|
||||||
|
|
||||||
logContainer.appendChild(logEntry);
|
|
||||||
logContainer.scrollTop = logContainer.scrollHeight;
|
|
||||||
}
|
|
||||||
|
|
||||||
function showNotification(message, type = 'success') {
|
|
||||||
const notification = document.getElementById('notification');
|
|
||||||
notification.textContent = message;
|
|
||||||
notification.className = `notification ${type}`;
|
|
||||||
notification.classList.add('show');
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
notification.classList.remove('show');
|
|
||||||
}, 3000);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fonctions d'action
|
|
||||||
function scanWifi() {
|
|
||||||
const scanBtn = document.getElementById('scanBtn');
|
|
||||||
const originalText = scanBtn.textContent;
|
|
||||||
|
|
||||||
scanBtn.innerHTML = '<div class="loading"></div> Scan en cours...';
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
updateWifiList();
|
|
||||||
scanBtn.textContent = originalText;
|
|
||||||
showNotification('Scan terminé - Réseaux mis à jour');
|
|
||||||
addLog("WiFi", "Scan des réseaux terminé");
|
|
||||||
}, 2000);
|
|
||||||
}
|
|
||||||
|
|
||||||
function connectToWifi() {
|
|
||||||
if (!appState.selectedWifi) {
|
|
||||||
showNotification('Veuillez sélectionner un réseau WiFi', 'error');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const password = document.getElementById('wifiPassword').value;
|
|
||||||
if (!password && appState.selectedWifi.security !== 'Open') {
|
|
||||||
showNotification('Mot de passe requis', 'error');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const connectBtn = document.getElementById('connectBtn');
|
|
||||||
const originalText = connectBtn.textContent;
|
|
||||||
|
|
||||||
connectBtn.innerHTML = '<div class="loading"></div> Connexion...';
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
connectBtn.textContent = originalText;
|
|
||||||
showNotification(`Connecté à ${appState.selectedWifi.ssid}`);
|
|
||||||
addLog("WiFi", `Connexion établie avec ${appState.selectedWifi.ssid}`);
|
|
||||||
|
|
||||||
// Mettre à jour le statut
|
|
||||||
document.getElementById('connectionStatus').innerHTML = `
|
|
||||||
<div class="status-dot"></div>
|
|
||||||
<span>Connecté à ${appState.selectedWifi.ssid}</span>
|
|
||||||
`;
|
|
||||||
}, 3000);
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateHotspot() {
|
|
||||||
const name = document.getElementById('hotspotName').value;
|
|
||||||
const password = document.getElementById('hotspotPassword').value;
|
|
||||||
const channel = document.getElementById('hotspotChannel').value;
|
|
||||||
|
|
||||||
if (!name || !password) {
|
|
||||||
showNotification('Nom et mot de passe requis', 'error');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
showNotification('Configuration du hotspot mise à jour');
|
|
||||||
addLog("Hotspot", `Configuration mise à jour: ${name} (Canal ${channel})`);
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggleHotspot() {
|
|
||||||
appState.hotspotEnabled = !appState.hotspotEnabled;
|
|
||||||
const btn = document.getElementById('hotspotBtn');
|
|
||||||
|
|
||||||
if (appState.hotspotEnabled) {
|
|
||||||
btn.textContent = 'Arrêter le hotspot';
|
|
||||||
showNotification('Hotspot activé');
|
|
||||||
addLog("Hotspot", "Hotspot activé");
|
|
||||||
} else {
|
|
||||||
btn.textContent = 'Démarrer le hotspot';
|
|
||||||
showNotification('Hotspot désactivé');
|
|
||||||
addLog("Hotspot", "Hotspot désactivé");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function clearLogs() {
|
|
||||||
document.getElementById('logContainer').innerHTML = '';
|
|
||||||
addLog("Système", "Logs effacés");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mises à jour périodiques
|
|
||||||
function startPeriodicUpdates() {
|
|
||||||
setInterval(updateStats, 1000);
|
|
||||||
setInterval(() => {
|
|
||||||
// Simulation de nouveaux logs
|
|
||||||
if (Math.random() > 0.95) {
|
|
||||||
const events = [
|
|
||||||
"Nouveau client connecté",
|
|
||||||
"Paquet routé vers l'extérieur",
|
|
||||||
"Vérification de la connexion",
|
|
||||||
"Mise à jour des tables de routage"
|
|
||||||
];
|
|
||||||
const randomEvent = events[Math.floor(Math.random() * events.length)];
|
|
||||||
addLog("Système", randomEvent);
|
|
||||||
}
|
|
||||||
}, 5000);
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
338
static/style.css
Normal file
338
static/style.css
Normal file
|
@ -0,0 +1,338 @@
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
min-height: 100vh;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
background: rgba(255, 255, 255, 0.95);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
border-radius: 20px;
|
||||||
|
padding: 30px;
|
||||||
|
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 40px;
|
||||||
|
padding-bottom: 20px;
|
||||||
|
border-bottom: 2px solid #e0e0e0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header h1 {
|
||||||
|
color: #333;
|
||||||
|
font-size: 2.5em;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-indicator {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 10px 20px;
|
||||||
|
border-radius: 25px;
|
||||||
|
font-weight: 500;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-online {
|
||||||
|
background: #d4edda;
|
||||||
|
color: #155724;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-offline {
|
||||||
|
background: #f8d7da;
|
||||||
|
color: #721c24;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-dot {
|
||||||
|
width: 12px;
|
||||||
|
height: 12px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: currentColor;
|
||||||
|
animation: pulse 2s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes pulse {
|
||||||
|
0%, 100% { opacity: 1; }
|
||||||
|
50% { opacity: 0.5; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
|
||||||
|
gap: 30px;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
background: white;
|
||||||
|
border-radius: 15px;
|
||||||
|
padding: 25px;
|
||||||
|
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
|
||||||
|
transition: transform 0.3s ease, box-shadow 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card:hover {
|
||||||
|
transform: translateY(-5px);
|
||||||
|
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card h2 {
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
font-size: 1.4em;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
fill: currentColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group label {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
color: #555;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group input, .form-group select {
|
||||||
|
width: 100%;
|
||||||
|
padding: 12px 16px;
|
||||||
|
border: 2px solid #e0e0e0;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 16px;
|
||||||
|
transition: border-color 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group input:focus, .form-group select:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: #667eea;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
padding: 12px 24px;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 500;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 10px 20px rgba(102, 126, 234, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn:active {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary {
|
||||||
|
background: #6c757d;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-danger {
|
||||||
|
background: #dc3545;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wifi-list {
|
||||||
|
max-height: 300px;
|
||||||
|
overflow-y: auto;
|
||||||
|
border: 1px solid #e0e0e0;
|
||||||
|
border-radius: 8px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wifi-item {
|
||||||
|
padding: 12px 16px;
|
||||||
|
border-bottom: 1px solid #f0f0f0;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.2s ease;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wifi-item:hover {
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wifi-item:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wifi-item.selected {
|
||||||
|
background-color: #e7f3ff;
|
||||||
|
border-left: 4px solid #667eea;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wifi-signal {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.signal-strength {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.signal-bars {
|
||||||
|
display: flex;
|
||||||
|
gap: 2px;
|
||||||
|
align-items: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.signal-bar {
|
||||||
|
width: 3px;
|
||||||
|
background: #ccc;
|
||||||
|
border-radius: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.signal-bar.active {
|
||||||
|
background: #28a745;
|
||||||
|
}
|
||||||
|
|
||||||
|
.devices-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||||
|
gap: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.device-card {
|
||||||
|
background: #f8f9fa;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 15px;
|
||||||
|
text-align: center;
|
||||||
|
transition: transform 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.device-card:hover {
|
||||||
|
transform: scale(1.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.device-icon {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
margin: 0 auto 10px;
|
||||||
|
fill: #667eea;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-container {
|
||||||
|
background: #1a1a1a;
|
||||||
|
color: #00ff00;
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 10px;
|
||||||
|
font-family: 'Courier New', monospace;
|
||||||
|
font-size: 14px;
|
||||||
|
max-height: 300px;
|
||||||
|
overflow-y: auto;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-entry {
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-timestamp {
|
||||||
|
color: #888;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stats-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-card {
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
color: white;
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 10px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-value {
|
||||||
|
font-size: 2em;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-label {
|
||||||
|
font-size: 0.9em;
|
||||||
|
opacity: 0.9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading {
|
||||||
|
display: inline-block;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
border: 3px solid #f3f3f3;
|
||||||
|
border-top: 3px solid #667eea;
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
0% { transform: rotate(0deg); }
|
||||||
|
100% { transform: rotate(360deg); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification {
|
||||||
|
position: fixed;
|
||||||
|
top: 20px;
|
||||||
|
right: 20px;
|
||||||
|
padding: 15px 20px;
|
||||||
|
border-radius: 10px;
|
||||||
|
color: white;
|
||||||
|
font-weight: 500;
|
||||||
|
z-index: 1000;
|
||||||
|
transform: translateX(100%);
|
||||||
|
transition: transform 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification.show {
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification.success {
|
||||||
|
background: #28a745;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification.error {
|
||||||
|
background: #dc3545;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue