|
|
| |
| let audioCtx = null; |
| let soundEnabled = false; |
| let ws = null; |
|
|
| |
| let currentUser = null; |
| let sessionId = null; |
| let accessLevel = null; |
| const ACTIVE_SESSIONS = new Map(); |
|
|
| |
| const API_BASE = 'http://localhost:3000/api'; |
|
|
| document.addEventListener('DOMContentLoaded', () => { |
| |
| |
| document.body.addEventListener('click', initAudio, { once: true }); |
| document.getElementById('btn-sound').addEventListener('click', toggleSound); |
|
|
| |
| setupAuthentication(); |
|
|
| |
| setupTerminal(); |
| setupAIChat(); |
|
|
| |
| if (typeof feather !== 'undefined') { |
| feather.replace(); |
| } |
| }); |
|
|
| |
| function setupAuthentication() { |
| const loginForm = document.getElementById('login-form'); |
| if (loginForm) { |
| loginForm.addEventListener('submit', async (e) => { |
| e.preventDefault(); |
| const user = document.getElementById('login-user').value; |
| const pass = document.getElementById('login-pass').value; |
| |
| await authenticateUser(user, pass); |
| }); |
| } |
| } |
|
|
| function checkAuthentication() { |
| const session = localStorage.getItem('cybershield_session'); |
| if (session) { |
| const sessionData = JSON.parse(session); |
| if (sessionData.expiry > Date.now()) { |
| restoreSession(sessionData); |
| } else { |
| localStorage.removeItem('cybershield_session'); |
| showLoginModal(); |
| } |
| } else { |
| showLoginModal(); |
| } |
| } |
|
|
| function showLoginModal() { |
| document.getElementById('login-modal').classList.remove('hidden'); |
| document.getElementById('main-container').classList.add('blur-sm'); |
| } |
|
|
| function hideLoginModal() { |
| document.getElementById('login-modal').classList.add('hidden'); |
| document.getElementById('main-container').classList.remove('blur-sm'); |
| document.getElementById('main-container').classList.add('pt-8'); |
| } |
|
|
| async function authenticateUser(username, password) { |
| |
| if (username.startsWith('ADMIN_') && password.length >= 8) { |
| currentUser = username; |
| sessionId = 'AUTH-' + Date.now() + '-' + Math.random().toString(36).substr(2, 9); |
| accessLevel = 'ADMIN_T3'; |
| |
| |
| const sessionData = { |
| user: currentUser, |
| sessionId: sessionId, |
| level: accessLevel, |
| expiry: Date.now() + (8 * 60 * 60 * 1000) |
| }; |
| localStorage.setItem('cybershield_session', JSON.stringify(sessionData)); |
| |
| |
| document.getElementById('current-user').textContent = currentUser; |
| document.getElementById('session-id').textContent = sessionId; |
| document.getElementById('session-bar').classList.remove('hidden'); |
| |
| hideLoginModal(); |
| |
| |
| addLogEntry(`Authentification réussie: ${username} (Niveau ${accessLevel})`, 'success', 'AUTH'); |
| |
| |
| initializeSystems(); |
| |
| |
| startAccessMonitoring(); |
| startEgressMonitoring(); |
| startIngressMonitoring(); |
| |
| playBeep(800, 'sine', 0.2); |
| return true; |
| } else { |
| addLogEntry(`Tentative d'authentification échouée: ${username}`, 'error', 'AUTH'); |
| alert('Identifiants invalides. Format requis: ADMIN_xxxx'); |
| return false; |
| } |
| } |
|
|
| function restoreSession(sessionData) { |
| currentUser = sessionData.user; |
| sessionId = sessionData.sessionId; |
| accessLevel = sessionData.level; |
| |
| document.getElementById('current-user').textContent = currentUser; |
| document.getElementById('session-id').textContent = sessionId; |
| document.getElementById('session-bar').classList.remove('hidden'); |
| document.getElementById('main-container').classList.add('pt-8'); |
| |
| initializeSystems(); |
| startAccessMonitoring(); |
| startEgressMonitoring(); |
| startIngressMonitoring(); |
| } |
|
|
| function logout() { |
| if (confirm('Confirmer la déconnexion ?')) { |
| localStorage.removeItem('cybershield_session'); |
| addLogEntry(`Déconnexion: ${currentUser}`, 'info', 'AUTH'); |
| location.reload(); |
| } |
| } |
|
|
| function forceLogoutAll() { |
| if (confirm('ÊTES-VOUS SÛR ? Cette action déconnectera tous les utilisateurs actifs.')) { |
| broadcastAlertToServer({ |
| type: 'FORCE_LOGOUT_ALL', |
| severity: 'CRITICAL', |
| description: 'Déconnexion forcée de toutes les sessions par administrateur', |
| timestamp: new Date().toISOString() |
| }); |
| addLogEntry('Déconnexion forcée de toutes les sessions', 'error', 'AUTH'); |
| } |
| } |
|
|
| function initializeSystems() { |
| |
| initLiveChart(); |
| initMap(); |
| |
| |
| connectWebSocket(); |
| |
| |
| fetchStats(); |
| setInterval(fetchStats, 1000); |
| startVulnStream(); |
| startInfraStream(); |
| startMapStream(); |
| initCognitiveEngine(); |
| startCognitiveStream(); |
| startLogStream(); |
| |
| |
| simulateUSBMonitoring(); |
| } |
|
|
| |
| function startAccessMonitoring() { |
| |
| updateSessionsTable(); |
| setInterval(updateSessionsTable, 5000); |
| |
| |
| simulateFileAccessMonitoring(); |
| } |
|
|
| function updateSessionsTable() { |
| const tbody = document.getElementById('sessions-table'); |
| if (!tbody) return; |
| |
| const sessions = [ |
| { user: currentUser, ip: '192.168.1.100', time: new Date().toLocaleTimeString(), level: 'ADMIN_T3', status: 'Active' }, |
| { user: 'AGENT_4521', ip: '192.168.1.45', time: '09:15:32', level: 'OPERATOR', status: 'Active' }, |
| { user: 'TECH_8842', ip: '10.0.0.15', time: '08:30:12', level: 'TECHNICIAN', status: 'Idle' } |
| ]; |
| |
| tbody.innerHTML = sessions.map(s => ` |
| <tr class="hover:bg-gray-800/30 transition"> |
| <td class="px-6 py-3 font-mono text-white">${s.user}</td> |
| <td class="px-6 py-3 font-mono text-cyber-primary">${s.ip}</td> |
| <td class="px-6 py-3 text-gray-400">${s.time}</td> |
| <td class="px-6 py-3"><span class="text-xs ${s.level === 'ADMIN_T3' ? 'text-red-400' : 'text-yellow-400'} border border-gray-700 px-2 py-1 rounded">${s.level}</span></td> |
| <td class="px-6 py-3"><span class="text-xs ${s.status === 'Active' ? 'text-green-400' : 'text-gray-500'} flex items-center"><span class="w-2 h-2 ${s.status === 'Active' ? 'bg-green-400 animate-pulse' : 'bg-gray-500'} rounded-full mr-2"></span>${s.status}</span></td> |
| <td class="px-6 py-3"> |
| ${s.user !== currentUser ? `<button onclick="terminateSession('${s.user}')" class="text-red-400 hover:text-red-300 text-xs border border-red-500/30 px-2 py-1 rounded hover:bg-red-500/20">Terminer</button>` : '<span class="text-xs text-gray-600">Current</span>'} |
| </td> |
| </tr> |
| `).join(''); |
| |
| document.getElementById('active-sessions').textContent = sessions.length; |
| document.getElementById('admin-sessions').textContent = sessions.filter(s => s.level === 'ADMIN_T3').length; |
| } |
|
|
| function terminateSession(username) { |
| addLogEntry(`Session terminée: ${username}`, 'warning', 'AUTH'); |
| updateSessionsTable(); |
| } |
|
|
| |
| function simulateUSBMonitoring() { |
| const usbDevices = [ |
| { name: 'SanDisk Cruzer 32GB', vid: '0781', pid: '5567', status: 'blocked', type: 'storage' }, |
| { name: 'Logitech Mouse', vid: '046d', pid: 'c52b', status: 'allowed', type: 'hid' }, |
| { name: 'Unknown Device', vid: '1234', pid: '5678', status: 'pending', type: 'unknown' } |
| ]; |
| |
| const container = document.getElementById('usb-monitor'); |
| if (!container) return; |
| |
| container.innerHTML = usbDevices.map(dev => ` |
| <div class="p-3 bg-gray-900 rounded border ${dev.status === 'blocked' ? 'border-red-500/50 bg-red-900/10' : (dev.status === 'pending' ? 'border-yellow-500/50' : 'border-green-500/30')} flex justify-between items-center animate-fade-in"> |
| <div class="flex items-center"> |
| <i data-feather="usb" class="w-4 h-4 ${dev.status === 'blocked' ? 'text-red-400' : (dev.status === 'pending' ? 'text-yellow-400' : 'text-green-400')} mr-2"></i> |
| <div> |
| <div class="text-sm text-gray-300">${dev.name}</div> |
| <div class="text-[10px] text-gray-500">VID:${dev.vid} PID:${dev.pid} | ${dev.type}</div> |
| </div> |
| </div> |
| <div class="flex items-center space-x-2"> |
| <span class="text-xs ${dev.status === 'blocked' ? 'text-red-400' : (dev.status === 'pending' ? 'text-yellow-400' : 'text-green-400')} uppercase">${dev.status}</span> |
| ${dev.status === 'pending' ? ` |
| <button onclick="authorizeUSB('${dev.vid}', '${dev.pid}')" class="text-green-400 hover:text-green-300 text-xs border border-green-500/30 px-2 py-1 rounded">Autoriser</button> |
| <button onclick="blockUSB('${dev.vid}', '${dev.pid}')" class="text-red-400 hover:text-red-300 text-xs border border-red-500/30 px-2 py-1 rounded">Bloquer</button> |
| ` : ''} |
| </div> |
| </div> |
| `).join(''); |
| |
| if (typeof feather !== 'undefined') feather.replace(); |
| } |
|
|
| function authorizeUSB(vid, pid) { |
| addLogEntry(`USB Autorisé: VID:${vid} PID:${pid}`, 'success', 'USB'); |
| simulateUSBMonitoring(); |
| } |
|
|
| function blockUSB(vid, pid) { |
| addLogEntry(`USB Bloqué: VID:${vid} PID:${pid}`, 'error', 'USB'); |
| simulateUSBMonitoring(); |
| } |
|
|
| function authorizeAllUSB() { |
| addLogEntry('Tous les périphériques USB autorisés', 'warning', 'USB'); |
| simulateUSBMonitoring(); |
| } |
|
|
| function blockAllUSB() { |
| addLogEntry('Tous les périphériques USB bloqués', 'error', 'USB'); |
| simulateUSBMonitoring(); |
| } |
|
|
| |
| function simulateFileAccessMonitoring() { |
| const sensitiveFiles = [ |
| { file: '/secure/taj/TAJ_2024.db', user: 'AGENT_4521', action: 'READ', time: new Date().toLocaleTimeString() }, |
| { file: '/secure/dgfip/FICoba_National.db', user: 'ADMIN_SYS', action: 'READ', time: new Date().toLocaleTimeString() }, |
| { file: '/secure/fpr/FPR_Access.log', user: 'SYSTEM', action: 'WRITE', time: new Date().toLocaleTimeString() } |
| ]; |
| |
| const container = document.getElementById('file-access-log'); |
| if (!container) return; |
| |
| container.innerHTML = sensitiveFiles.map(f => ` |
| <div class="flex justify-between items-center p-2 bg-gray-900/50 rounded border-l-2 ${f.action === 'WRITE' ? 'border-red-500' : 'border-cyber-primary'} mb-1"> |
| <div class="flex items-center space-x-2"> |
| <i data-feather="file" class="w-3 h-3 text-gray-400"></i> |
| <span class="text-gray-300">${f.file.split('/').pop()}</span> |
| <span class="text-[10px] text-gray-500">${f.user}</span> |
| </div> |
| <span class="text-[10px] ${f.action === 'WRITE' ? 'text-red-400' : 'text-cyber-primary'} border border-gray-700 px-1 rounded">${f.action}</span> |
| </div> |
| `).join(''); |
| } |
|
|
| |
| function startIngressMonitoring() { |
| const container = document.getElementById('ingress-logs'); |
| if (!container) return; |
| |
| const ingressEvents = [ |
| { type: 'CONNECTION', src: '185.220.101.42:4444', dst: '192.168.1.100:443', status: 'BLOCKED', proto: 'TCP' }, |
| { type: 'DOWNLOAD', file: 'update.exe', size: '2.4MB', status: 'SCANNING', user: 'AGENT_4521' }, |
| { type: 'EMAIL', from: 'suspect@phishing.ru', to: 'admin@interieur.fr', status: 'QUARANTINED', subject: 'Urgent: Mise à jour' }, |
| { type: 'AUTH', user: 'TECH_8842', src: '10.0.0.15', status: 'SUCCESS', method: 'RSA-Key' } |
| ]; |
| |
| setInterval(() => { |
| const evt = ingressEvents[Math.floor(Math.random() * ingressEvents.length)]; |
| const time = new Date().toLocaleTimeString(); |
| |
| let colorClass = 'text-gray-400'; |
| let statusColor = 'text-gray-500'; |
| |
| if (evt.status === 'BLOCKED' || evt.status === 'QUARANTINED') { |
| colorClass = 'text-red-400'; |
| statusColor = 'text-red-400'; |
| } else if (evt.status === 'SUCCESS') { |
| colorClass = 'text-green-400'; |
| statusColor = 'text-green-400'; |
| } else if (evt.status === 'SCANNING') { |
| colorClass = 'text-yellow-400'; |
| statusColor = 'text-yellow-400'; |
| } |
| |
| const line = document.createElement('div'); |
| line.className = `font-mono text-xs ${colorClass} border-l-2 ${evt.status === 'BLOCKED' ? 'border-red-500 bg-red-900/10' : 'border-gray-700'} pl-2 py-1`; |
| line.innerHTML = ` |
| <span class="text-gray-600">[${time}]</span> |
| <span class="uppercase font-bold ml-1">${evt.type}</span> |
| <span class="ml-2">${evt.src || evt.file || evt.from} → ${evt.dst || evt.user || evt.to}</span> |
| <span class="${statusColor} ml-2 border border-gray-700 px-1 rounded text-[10px]">${evt.status}</span> |
| `; |
| |
| container.prepend(line); |
| if (container.children.length > 50) container.lastChild.remove(); |
| |
| if (evt.status === 'BLOCKED' || evt.status === 'QUARANTINED') { |
| addLogEntry(`Ingress bloqué: ${evt.type} depuis ${evt.src || evt.from}`, 'error', 'INGRESS'); |
| } |
| }, 3000); |
| } |
|
|
| |
| function startEgressMonitoring() { |
| let totalVolume = 0; |
| |
| setInterval(() => { |
| |
| const volume = Math.random() * 50; |
| totalVolume += volume; |
| |
| const isBlocked = Math.random() > 0.8; |
| const isSuspicious = Math.random() > 0.7; |
| |
| const blockedEl = document.getElementById('blocked-files'); |
| const suspectEl = document.getElementById('suspect-conn'); |
| const volumeEl = document.getElementById('egress-volume'); |
| const rateEl = document.getElementById('egress-rate'); |
| |
| if (blockedEl && isBlocked) { |
| blockedEl.textContent = parseInt(blockedEl.textContent || 0) + 1; |
| addEgressAlert('Tentative d\'exfiltration bloquée', 'Fichier TAJ détecté dans flux sortant', 'CRITICAL'); |
| } |
| |
| if (suspectEl && isSuspicious) { |
| suspectEl.textContent = parseInt(suspectEl.textContent || 0) + 1; |
| addEgressAlert('Connexion sortante suspecte', 'DNS tunneling détecté vers 185.220.101.42', 'HIGH'); |
| } |
| |
| |
| if (volumeEl) volumeEl.textContent = totalVolume.toFixed(1) + ' MB'; |
| if (rateEl) rateEl.textContent = '+' + volume.toFixed(1) + ' MB/s'; |
| |
| |
| updateEgressAnalysis(); |
| }, 2000); |
| } |
|
|
| function addEgressAlert(title, detail, severity) { |
| const container = document.getElementById('egress-alerts-log'); |
| const alertsEl = document.getElementById('egress-alerts'); |
| if (!container || !alertsEl) return; |
| |
| const alerts = parseInt(alertsEl.textContent || 0); |
| alertsEl.textContent = alerts + 1; |
| |
| const line = document.createElement('div'); |
| line.className = `p-2 rounded border-l-2 ${severity === 'CRITICAL' ? 'border-red-500 bg-red-900/20' : 'border-yellow-500 bg-yellow-900/10'} animate-fade-in`; |
| line.innerHTML = ` |
| <div class="flex justify-between items-start"> |
| <div> |
| <div class="text-sm font-bold ${severity === 'CRITICAL' ? 'text-red-400' : 'text-yellow-400'}">${title}</div> |
| <div class="text-xs text-gray-400">${detail}</div> |
| </div> |
| <span class="text-[10px] text-gray-500">${new Date().toLocaleTimeString()}</span> |
| </div> |
| `; |
| |
| container.prepend(line); |
| if (container.children.length > 20) container.lastChild.remove(); |
| |
| |
| if (severity === 'CRITICAL') { |
| addLogEntry(`EGRESS ALERT: ${title} - ${detail}`, 'error', 'DLP'); |
| playAlertSound(); |
| } |
| } |
|
|
| function updateEgressAnalysis() { |
| const container = document.getElementById('egress-analysis'); |
| if (!container) return; |
| |
| const processes = [ |
| { name: 'chrome.exe', status: 'Autorisé', width: 15, color: 'bg-green-500', dest: 'google.com', size: '1.2 MB' }, |
| { name: 'outlook.exe', status: 'Limité', width: 45, color: 'bg-yellow-500', dest: 'smtp.office365.com', size: '8.4 MB' }, |
| { name: 'svchost.exe', status: 'Surveillance', width: 30, color: 'bg-orange-500', dest: 'windowsupdate.com', size: '2.1 MB' }, |
| { name: 'unknown_process', status: 'Bloqué', width: 0, color: 'bg-red-500', dest: 'BLOCKED', size: '0 MB' } |
| ]; |
| |
| container.innerHTML = processes.map(p => ` |
| <div class="p-3 bg-gray-900 rounded border border-gray-700"> |
| <div class="flex justify-between mb-2"> |
| <span class="text-sm text-gray-300">${p.name}</span> |
| <span class="text-xs ${p.status === 'Bloqué' ? 'text-red-400' : (p.status === 'Limité' ? 'text-yellow-400' : 'text-green-400')}">${p.status}</span> |
| </div> |
| <div class="w-full bg-gray-800 rounded-full h-2"> |
| <div class="${p.color} h-2 rounded-full transition-all duration-1000" style="width: ${p.width}%"></div> |
| </div> |
| <div class="flex justify-between text-xs text-gray-500 mt-1"> |
| <span>${p.dest}</span> |
| <span>${p.size}</span> |
| </div> |
| </div> |
| `).join(''); |
| } |
|
|
| function enableStrictMode() { |
| if (confirm('Activer le mode STRICT ? Tous les flux sortants non essentiels seront bloqués.')) { |
| addLogEntry('MODE STRICT activé - Egress filtering renforcé', 'error', 'DLP'); |
| addEgressAlert('Mode Strict Activé', 'Tous les transferts > 1Mo nécessitent autorisation', 'INFO'); |
| } |
| } |
|
|
| function clearEgressAlerts() { |
| const alertsEl = document.getElementById('egress-alerts'); |
| if (alertsEl) alertsEl.textContent = '0'; |
| const container = document.getElementById('egress-alerts-log'); |
| if (container) container.innerHTML = ''; |
| } |
|
|
| function refreshAccessLogs() { |
| addLogEntry('Rafraîchissement des logs d\'accès...', 'info', 'AUTH'); |
| updateSessionsTable(); |
| simulateFileAccessMonitoring(); |
| simulateUSBMonitoring(); |
| } |
|
|
| |
| function connectWebSocket() { |
| const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; |
| const wsUrl = `${protocol}//${window.location.hostname}:3000`; |
| |
| console.log('🔌 Connexion WebSocket:', wsUrl); |
| |
| ws = new WebSocket(wsUrl); |
| |
| ws.onopen = () => { |
| console.log('🔌 Connecté au SOC temps réel'); |
| updateSystemStatus('online'); |
| }; |
| |
| ws.onmessage = (event) => { |
| const data = JSON.parse(event.data); |
| handleRealtimeAlert(data); |
| }; |
| |
| ws.onerror = (error) => { |
| console.error('WebSocket erreur:', error); |
| updateSystemStatus('error'); |
| }; |
| |
| ws.onclose = () => { |
| console.log('Déconnecté - Reconnexion dans 5s...'); |
| updateSystemStatus('offline'); |
| setTimeout(connectWebSocket, 5000); |
| }; |
| } |
|
|
| function handleRealtimeAlert(data) { |
| |
| if (!data) return; |
| |
| |
| if (data.severity === 'CRITICAL' || data.severity === 'HIGH') { |
| playAlertSound(); |
| showToastAlert(data); |
| |
| if (data.type && (data.type.includes('EXFILTRATION') || data.type.includes('EMAIL') || data.type.includes('SENSITIVE'))) { |
| showEmergencyAlert(data); |
| } |
| } |
| |
| |
| switch(data.type) { |
| case 'SUSPICIOUS_CONNECTION': |
| if (data.data && data.data.peer) { |
| addLogEntry(`Connexion suspecte: ${data.data.peer}:${data.data.peerPort || 'N/A'}`, 'error', data.data.peer); |
| } |
| break; |
| case 'BRUTE_FORCE_DETECTED': |
| const count = data.count || 0; |
| const ip = (data.ips && data.ips.length > 0) ? data.ips[0] : 'UNKNOWN'; |
| addLogEntry(`Brute Force (${count} tentatives)`, 'error', ip); |
| incrementThreatCounter(count); |
| break; |
| case 'IP_BLOCKED': |
| addLogEntry(`IP bloquée: ${data.ip || 'UNKNOWN'}`, 'success', 'SYSTEM'); |
| break; |
| case 'PROCESS_ALERT': |
| if (data.data && data.data.length > 0 && data.data[0].name) { |
| addLogEntry(`Processus suspect: ${data.data[0].name}`, 'warning', 'LOCAL'); |
| } |
| break; |
| |
| case 'DATA_EXFILTRATION_DETECTED': |
| addLogEntry(`🚨 EXFILTRATION: ${data.description || 'Données sensibles'}`, 'error', 'TAJ/FPR'); |
| incrementThreatCounter(10); |
| break; |
| case 'EMAIL_COMPROMISE': |
| if (data.data) { |
| const user = data.data.user || 'UNKNOWN'; |
| const country = data.data.country || 'UNKNOWN'; |
| const ip = data.data.ip || 'UNKNOWN'; |
| addLogEntry(`📧 MESSAGERIE COMPROMISE: ${user} depuis ${country}`, 'error', ip); |
| } |
| break; |
| case 'SENSITIVE_FILE_ACCESS': |
| if (data.data) { |
| const isBanking = data.data.type === 'FINANCES'; |
| const prefix = isBanking ? '🏦 FICoba/DGFiP:' : '🔒'; |
| const file = data.data.file || 'UNKNOWN'; |
| const volume = data.data.volume || 'N/A'; |
| addLogEntry(`${prefix} ACCÈS ${file} (${volume})`, 'error', data.data.type || 'SYSTEM'); |
| if (isBanking) { |
| const records = data.data.records || 'N/A'; |
| showEmergencyAlert({ |
| type: 'FICOBa_BREACH_ATTEMPT', |
| severity: 'CRITICAL', |
| description: `Tentative d'accès au fichier des comptes bancaires: ${file}. ${records} comptes potentiellement concernés.`, |
| timestamp: data.timestamp || new Date().toISOString() |
| }); |
| } |
| } |
| break; |
| case 'CREDENTIAL_STOLEN': |
| if (data.data) { |
| const user = data.data.user || 'UNKNOWN'; |
| const source = data.data.source || 'UNKNOWN'; |
| addLogEntry(`🔑 CODE ACCÈS VOLÉ: ${user} via ${source}`, 'warning', 'AUTH'); |
| } |
| break; |
| case 'USB_DETECTED': |
| if (data.device) { |
| addLogEntry(`USB Détecté: ${data.device.model || 'Unknown'}`, 'info', 'USB'); |
| } |
| break; |
| case 'USB_BLOCKED': |
| if (data.device) { |
| addLogEntry(`USB Bloqué: ${data.device.model || 'Unknown'}`, 'error', 'USB'); |
| } |
| break; |
| case 'FILE_ACCESS': |
| case 'FILE_MODIFIED': |
| case 'SENSITIVE_FILE_CREATED': |
| if (data.file) { |
| addLogEntry(`Fichier: ${data.type} - ${data.file}`, data.severity === 'CRITICAL' ? 'error' : 'warning', 'FS'); |
| } |
| break; |
| } |
| } |
|
|
| |
| function showEmergencyAlert(data) { |
| if (!data) return; |
| |
| const existing = document.getElementById('emergency-alert'); |
| if (existing) existing.remove(); |
| |
| const alertType = data.type || 'UNKNOWN_ALERT'; |
| const description = data.description || 'Menace détectée sur infrastructure sensible'; |
| |
| const alert = document.createElement('div'); |
| alert.id = 'emergency-alert'; |
| alert.className = 'fixed inset-0 bg-red-900/90 z-[100] flex items-center justify-center backdrop-blur-sm animate-fade-in'; |
| alert.innerHTML = ` |
| <div class="bg-black border-2 border-red-500 rounded-xl p-8 max-w-2xl shadow-[0_0_50px_rgba(239,68,68,0.5)]"> |
| <div class="flex items-center mb-4"> |
| <i data-feather="alert-octagon" class="w-12 h-12 text-red-500 mr-4 animate-pulse"></i> |
| <div> |
| <h2 class="text-2xl font-bold text-red-500 uppercase">Alerte Critique Administration</h2> |
| <p class="text-red-300 font-mono">${new Date().toLocaleTimeString('fr-FR')}</p> |
| </div> |
| </div> |
| <div class="bg-red-950/50 p-4 rounded border border-red-500/30 mb-6"> |
| <p class="text-white text-lg font-bold mb-2">${alertType.replace(/_/g, ' ')}</p> |
| <p class="text-gray-300">${description}</p> |
| </div> |
| <div class="flex space-x-4"> |
| <button onclick="this.closest('#emergency-alert').remove()" class="flex-1 bg-red-600 hover:bg-red-700 text-white py-3 rounded font-bold transition"> |
| Acquitter l'alerte |
| </button> |
| <button onclick="isolateSystem(); this.closest('#emergency-alert').remove()" class="flex-1 bg-gray-800 hover:bg-gray-700 border border-red-500 text-red-400 py-3 rounded font-bold transition"> |
| Isoler le Système |
| </button> |
| </div> |
| </div> |
| `; |
| document.body.appendChild(alert); |
| if (typeof feather !== 'undefined') feather.replace(); |
| } |
|
|
| |
| async function blockIP(ip) { |
| try { |
| const response = await fetch('http://localhost:3000/api/block-ip', { |
| method: 'POST', |
| headers: { 'Content-Type': 'application/json' }, |
| body: JSON.stringify({ ip: ip, reason: 'Attaque administration française' }) |
| }); |
| const data = await response.json(); |
| addLogEntry(`IP ${ip} bloquée définitivement`, 'success', 'FIREWALL'); |
| } catch (e) { |
| addLogEntry(`Échec blocage IP ${ip}`, 'error', 'SYSTEM'); |
| } |
| } |
|
|
| function isolateSystem() { |
| addLogEntry('🔒 ISOLEMENT DU SYSTÈME ENGAGÉ', 'error', 'ADMIN'); |
| showToastAlert({ |
| type: 'SYSTEM_ISOLATION', |
| severity: 'CRITICAL', |
| description: 'Réseau segmenté, accès externes coupés', |
| timestamp: new Date().toISOString() |
| }); |
| } |
|
|
| function revokeCredentials() { |
| addLogEntry('🔑 RÉVOCATION MASSIVE DES ACCÈS', 'warning', 'ADMIN'); |
| alert('Tous les tokens de session ont été révoqués. Reconnexion requise pour tous les agents.'); |
| } |
|
|
| function exportReport() { |
| const report = { |
| date: new Date().toISOString(), |
| administration: 'Ministère de l\'Intérieur (Simulation)', |
| incidents: [], |
| status: 'Rapport généré par CyberShield SOC' |
| }; |
| console.log('Rapport exporté:', report); |
| addLogEntry('📄 Rapport d\'incident exporté', 'info', 'REPORT'); |
| alert('Rapport d\'incident exporté vers le SI-Central'); |
| } |
|
|
| function showToastAlert(data) { |
| if (!data) return; |
| |
| |
| const toast = document.createElement('div'); |
| toast.className = 'fixed top-4 right-4 bg-red-600 text-white px-6 py-4 rounded-lg shadow-2xl z-50 animate-slide-in border-l-4 border-red-300 flex items-center'; |
| toast.style.animation = 'slideInRight 0.3s ease-out'; |
| toast.innerHTML = ` |
| <i data-feather="alert-triangle" class="mr-3"></i> |
| <div> |
| <div class="font-bold text-sm uppercase">${data.type || 'ALERTE'}</div> |
| <div class="text-xs opacity-90">${data.timestamp ? new Date(data.timestamp).toLocaleTimeString('fr-FR') : new Date().toLocaleTimeString('fr-FR')}</div> |
| </div> |
| `; |
| document.body.appendChild(toast); |
| if (typeof feather !== 'undefined') feather.replace(); |
| |
| setTimeout(() => { |
| toast.remove(); |
| }, 5000); |
| } |
|
|
| function updateSystemStatus(status) { |
| const statusEl = document.getElementById('status-text'); |
| if (!statusEl) return; |
| |
| const indicator = statusEl.parentElement.nextElementSibling; |
| |
| if (status === 'online') { |
| statusEl.innerText = 'Connecté - Temps Réel (PROD)'; |
| statusEl.className = 'text-green-400 font-mono font-bold'; |
| if (indicator) indicator.className = 'w-2 h-2 bg-green-400 rounded-full animate-pulse'; |
| } else if (status === 'error') { |
| statusEl.innerText = 'Erreur Connexion'; |
| statusEl.className = 'text-red-400 font-mono'; |
| if (indicator) indicator.className = 'w-2 h-2 bg-red-500 rounded-full'; |
| } else { |
| statusEl.innerText = 'Hors ligne'; |
| statusEl.className = 'text-gray-400 font-mono'; |
| if (indicator) indicator.className = 'w-2 h-2 bg-gray-500 rounded-full'; |
| } |
| } |
|
|
| function incrementThreatCounter(count = 1) { |
| |
| const counter = document.querySelector('.counter[data-target]') || document.querySelector('.counter'); |
| if (counter) { |
| let current = parseInt(counter.innerText.replace(/,/g, '')) || 0; |
| current += count; |
| counter.innerText = current.toLocaleString(); |
| } |
| } |
|
|
| |
| function initAudio() { |
| if (!audioCtx) { |
| audioCtx = new (window.AudioContext || window.webkitAudioContext)(); |
| } |
| } |
|
|
| function toggleSound() { |
| soundEnabled = !soundEnabled; |
| const btn = document.getElementById('btn-sound'); |
| const icon = btn.querySelector('i'); |
| |
| if (soundEnabled) { |
| icon.setAttribute('data-feather', 'volume-2'); |
| icon.classList.replace('text-gray-400', 'text-cyber-primary'); |
| playBeep(600, 'sine', 0.1); |
| } else { |
| icon.setAttribute('data-feather', 'volume-x'); |
| icon.classList.replace('text-cyber-primary', 'text-gray-400'); |
| } |
| feather.replace(); |
| } |
|
|
| function playAlertSound() { |
| if (!soundEnabled || !audioCtx) return; |
| |
| const osc = audioCtx.createOscillator(); |
| const gain = audioCtx.createGain(); |
| |
| osc.type = 'sawtooth'; |
| osc.frequency.setValueAtTime(440, audioCtx.currentTime); |
| osc.frequency.exponentialRampToValueAtTime(880, audioCtx.currentTime + 0.1); |
| |
| gain.gain.setValueAtTime(0.1, audioCtx.currentTime); |
| gain.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + 0.3); |
| |
| osc.connect(gain); |
| gain.connect(audioCtx.destination); |
| osc.start(); |
| osc.stop(audioCtx.currentTime + 0.3); |
| } |
| |
| function navigateTo(viewId) { |
| |
| document.querySelectorAll('[id^="view-"]').forEach(el => el.classList.add('hidden')); |
| |
| document.getElementById(`view-${viewId}`).classList.remove('hidden'); |
| |
| |
| document.querySelectorAll('.nav-link').forEach(el => { |
| el.classList.remove('active', 'bg-gray-800/50', 'text-cyber-primary', 'border-gray-700'); |
| el.classList.add('text-gray-400'); |
| }); |
| |
| |
| const activeNav = document.getElementById(`nav-${viewId}`); |
| if(activeNav) { |
| activeNav.classList.add('active', 'bg-gray-800/50', 'text-cyber-primary', 'border-gray-700'); |
| activeNav.classList.remove('text-gray-400'); |
| } |
|
|
| |
| feather.replace(); |
| } |
| |
| let trafficHistory = []; |
|
|
| function initLiveChart() { |
| const chartContainer = document.getElementById('liveChart'); |
| const barCount = 50; |
| |
| |
| for(let i = 0; i < barCount; i++) { |
| const bar = document.createElement('div'); |
| bar.className = 'bg-cyber-primary w-1.5 rounded-t-sm opacity-60 transition-all duration-300 ease-in-out hover:opacity-100 hover:bg-white'; |
| bar.style.height = '10%'; |
| chartContainer.appendChild(bar); |
| trafficHistory.push(10); |
| } |
| } |
|
|
| function updateTrafficChart(currentTraffic) { |
| const chartContainer = document.getElementById('liveChart'); |
| if (!chartContainer || chartContainer.children.length === 0) return; |
| |
| |
| trafficHistory.shift(); |
| trafficHistory.push(currentTraffic); |
| |
| |
| const bars = chartContainer.children; |
| const maxTraffic = Math.max(...trafficHistory, 100); |
| |
| for (let i = 0; i < bars.length; i++) { |
| const percentage = (trafficHistory[i] / maxTraffic) * 100; |
| bars[i].style.height = `${Math.max(percentage, 5)}%`; |
| |
| |
| if (percentage > 80) { |
| bars[i].className = 'bg-red-500 w-1.5 rounded-t-sm shadow-[0_0_10px_rgba(239,68,68,0.8)] transition-all duration-300'; |
| } else if (percentage > 50) { |
| bars[i].className = 'bg-yellow-400 w-1.5 rounded-t-sm transition-all duration-300'; |
| } else { |
| bars[i].className = 'bg-cyber-primary w-1.5 rounded-t-sm opacity-60 transition-all duration-300 ease-in-out hover:opacity-100 hover:bg-white'; |
| } |
| } |
| } |
| |
| let previousAlertCount = 0; |
|
|
| async function fetchStats() { |
| try { |
| const response = await fetch('http://localhost:3000/api/stats'); |
| if (!response.ok) throw new Error('Network response was not ok'); |
| const data = await response.json(); |
| |
| |
| if (data.criticalAlerts > previousAlertCount) { |
| playAlertSound(); |
| |
| document.querySelectorAll('.counter')[3].parentElement.parentElement.classList.add('pulse-alert'); |
| setTimeout(() => { |
| document.querySelectorAll('.counter')[3].parentElement.parentElement.classList.remove('pulse-alert'); |
| }, 2000); |
| } |
| previousAlertCount = data.criticalAlerts; |
|
|
| |
| updateCounterAnimated(document.querySelector('.counter'), data.threatsBlocked); |
| |
| |
| const healthEl = document.querySelectorAll('.counter')[1].nextElementSibling; |
| healthEl.innerText = data.serverHealth + "%"; |
| if (data.serverHealth < 50) healthEl.className = 'text-3xl font-bold text-red-500 mt-2'; |
| else if (data.serverHealth < 80) healthEl.className = 'text-3xl font-bold text-yellow-400 mt-2'; |
| else healthEl.className = 'text-3xl font-bold text-cyber-secondary mt-2'; |
| |
| |
| document.querySelectorAll('.counter')[2].previousElementSibling.lastElementChild.innerText = data.networkTraffic + " MB/s"; |
| updateTrafficChart(data.networkTraffic); |
| |
| |
| const alertEl = document.querySelectorAll('.counter')[3]; |
| alertEl.innerText = data.criticalAlerts.toString().padStart(2, '0'); |
| if (data.criticalAlerts > 0) { |
| alertEl.classList.add('text-red-500'); |
| } |
|
|
| } catch (error) { |
| console.error('Erreur fetch stats:', error); |
| updateSystemStatus('error'); |
| } |
| } |
|
|
| function updateCounterAnimated(element, targetValue) { |
| if (!element) return; |
| const current = parseInt(element.innerText.replace(/,/g, '')) || 0; |
| if (current !== targetValue) { |
| element.innerText = targetValue.toLocaleString(); |
| |
| element.style.transform = 'scale(1.1)'; |
| setTimeout(() => element.style.transform = 'scale(1)', 200); |
| } |
| } |
| |
| function addLogEntry(message, type, ip) { |
| const container = document.getElementById('logContainer'); |
| if (!container) return; |
| |
| const logLine = document.createElement('div'); |
| logLine.className = 'log-entry flex justify-between border-b border-gray-900 pb-1 mb-1 animate-fade-in'; |
| |
| let colorClass = 'text-gray-400'; |
| if(type === 'error') colorClass = 'text-red-400'; |
| if(type === 'success') colorClass = 'text-green-400'; |
| if(type === 'warning') colorClass = 'text-yellow-400'; |
|
|
| const time = new Date().toLocaleTimeString('fr-FR', { hour12: false }); |
| |
| logLine.innerHTML = ` |
| <span class="${colorClass} font-mono text-xs">[${time}] ${message} (${ip || 'SYSTEM'})</span> |
| <span class="text-gray-600 text-xs">${type.toUpperCase()}</span> |
| `; |
| |
| container.prepend(logLine); |
| |
| |
| while(container.children.length > 50) { |
| container.lastElementChild.remove(); |
| } |
| } |
|
|
| async function startLogStream() { |
| |
| try { |
| const response = await fetch('http://localhost:3000/api/log'); |
| const data = await response.json(); |
| addLogEntry(data.msg, data.type, data.ip); |
| } catch (e) { |
| addLogEntry('Initialisation du système de logs...', 'info', 'LOCAL'); |
| } |
| } |
| |
| function simulateFallbackData() { |
| |
| const chartContainer = document.getElementById('liveChart'); |
| if(!chartContainer.children.length) initLiveChart(); |
| |
| const bars = chartContainer.children; |
| for(let i = 0; i < bars.length - 1; i++) { |
| bars[i].style.height = bars[i+1].style.height; |
| } |
| const lastBar = bars[bars.length - 1]; |
| const newHeight = Math.floor(Math.random() * 90) + 10; |
| lastBar.style.height = `${newHeight}%`; |
| |
| if(newHeight > 80) { |
| lastBar.className = 'bg-red-500 w-2 rounded-t-sm shadow-[0_0_10px_rgba(239,68,68,0.8)] transition-all duration-300'; |
| } else if (newHeight > 50) { |
| lastBar.className = 'bg-yellow-400 w-2 rounded-t-sm transition-all duration-300'; |
| } else { |
| lastBar.className = 'bg-cyber-primary w-2 rounded-t-sm opacity-50 transition-all duration-300'; |
| } |
| } |
|
|
| |
| |
| function initMap() { |
| |
| const svg = document.getElementById('map-svg-layer'); |
| while (svg.lastChild) { |
| svg.removeChild(svg.lastChild); |
| } |
| } |
| async function startMapStream() { |
| const container = document.getElementById('geo-log-container'); |
| const mapArea = document.getElementById('attack-map-container'); |
| |
| if (!container || !mapArea) return; |
| |
| if (!document.getElementById('map-svg-layer')) { |
| const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg"); |
| svg.id = "map-svg-layer"; |
| svg.style.position = "absolute"; |
| svg.style.top = "0"; |
| svg.style.left = "0"; |
| svg.style.width = "100%"; |
| svg.style.height = "100%"; |
| svg.style.pointerEvents = "none"; |
| svg.style.zIndex = "5"; |
| mapArea.appendChild(svg); |
| } |
| const svg = document.getElementById('map-svg-layer'); |
| |
| const fetchMapData = async () => { |
| try { |
| const response = await fetch('http://localhost:3000/api/geo'); |
| const data = await response.json(); |
|
|
| |
| const logLine = document.createElement('div'); |
| logLine.className = 'border-b border-gray-900 pb-1 text-xs font-mono animate-fade-in flex justify-between items-center'; |
| logLine.innerHTML = ` |
| <div> |
| <span class="bg-red-500/20 text-red-400 px-1 rounded text-[10px] mr-1">${data.country}</span> |
| <span class="text-gray-300">${data.type}</span> |
| <span class="text-gray-500">from ${data.ip}</span> |
| </div> |
| <span class="text-[10px] text-gray-600">${new Date(data.timestamp).toLocaleTimeString('fr-FR')}</span> |
| `; |
| container.prepend(logLine); |
| if(container.children.length > 20) container.lastChild.remove(); |
|
|
| |
| const node = document.createElement('div'); |
| node.className = 'geo-node'; |
| const top = Math.random() * 80 + 10; |
| const left = Math.random() * 80 + 10; |
| |
| node.style.top = `${top}%`; |
| node.style.left = `${left}%`; |
| mapArea.appendChild(node); |
|
|
| const line = document.createElementNS("http://www.w3.org/2000/svg", "line"); |
| line.setAttribute("x1", `${left}%`); |
| line.setAttribute("y1", `${top}%`); |
| line.setAttribute("x2", "50%"); |
| line.setAttribute("y2", "50%"); |
| line.setAttribute("stroke", data.real ? "#ff0000" : "#00f0ff"); |
| line.setAttribute("stroke-width", "2"); |
| line.setAttribute("stroke-opacity", "0.8"); |
| line.setAttribute("stroke-dasharray", "5,5"); |
| |
| const animate = document.createElementNS("http://www.w3.org/2000/svg", "animate"); |
| animate.setAttribute("attributeName", "stroke-dashoffset"); |
| animate.setAttribute("from", "100"); |
| animate.setAttribute("to", "0"); |
| animate.setAttribute("dur", "0.5s"); |
| animate.setAttribute("repeatCount", "1"); |
| |
| line.appendChild(animate); |
| svg.appendChild(line); |
|
|
| |
| const target = document.createElement('div'); |
| target.className = 'absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 w-4 h-4 bg-red-500 rounded-full animate-ping'; |
| mapArea.appendChild(target); |
|
|
| setTimeout(() => { |
| node.remove(); |
| line.remove(); |
| target.remove(); |
| }, 2000); |
|
|
| } catch(e) { |
| console.error('Erreur carte:', e); |
| } |
| }; |
| |
| |
| setInterval(fetchMapData, 2000); |
| } |
|
|
| |
| function toggleAIChat() { |
| const modal = document.getElementById('ai-chat-modal'); |
| |
| if (modal.classList.contains('hidden')) { |
| |
| modal.classList.remove('hidden'); |
| |
| setTimeout(() => { |
| modal.classList.remove('translate-y-4', 'opacity-0'); |
| }, 10); |
| document.getElementById('ai-input').focus(); |
| } else { |
| |
| modal.classList.add('translate-y-4', 'opacity-0'); |
| |
| setTimeout(() => { |
| modal.classList.add('hidden'); |
| }, 300); |
| } |
| } |
| function setupAIChat() { |
| const input = document.getElementById('ai-input'); |
| input.addEventListener('keypress', function (e) { |
| if (e.key === 'Enter') sendAIMessage(); |
| }); |
| } |
|
|
| function sendAIMessage() { |
| const input = document.getElementById('ai-input'); |
| const text = input.value.trim(); |
| if(!text) return; |
|
|
| const history = document.getElementById('chat-history'); |
| |
| |
| const userMsg = ` |
| <div class="flex items-start justify-end"> |
| <div class="bg-cyber-primary/20 p-2 rounded-lg rounded-tr-none border border-cyber-primary/30 text-white max-w-[90%]"> |
| ${text} |
| </div> |
| </div>`; |
| history.innerHTML += userMsg; |
|
|
| input.value = ''; |
| history.scrollTop = history.scrollHeight; |
|
|
| |
| setTimeout(() => { |
| let response = "Analyse en cours..."; |
| const lowerText = text.toLowerCase(); |
| |
| if(lowerText.includes("menace") || lowerText.includes("threat")) { |
| response = "J'ai analysé les 30 dernières minutes. Une tentative d'intrusion de type 'Brute Force' a été détectée sur le port 22. Elle a été bloquée automatiquement."; |
| } else if (lowerText.includes("serveur") || lowerText.includes("server")) { |
| response = "Les serveurs、主 인프라 sont sains. L'utilisation du CPU est stable à 24%. Aucun goulot d'étranglement détecté."; |
| } else if (lowerText.includes("optimiser") || lowerText.includes("optimize")) { |
| response = "Recommandation: Activer le protocole de mise en cache avancées sur le noeud DB-01 pour réduire la latence de 15%."; |
| } else { |
| response = "Données non traitées. Voulez-vous que je lance une analyse approfondie du pare-feu ?"; |
| } |
|
|
| const aiMsg = ` |
| <div class="flex items-start"> |
| <div class="bg-purple-900/30 p-2 rounded-lg rounded-tl-none border border-purple-500/30 text-gray-300 max-w-[90%]"> |
| ${response} |
| </div> |
| </div>`; |
| history.innerHTML += aiMsg; |
| history.scrollTop = history.scrollHeight; |
| feather.replace(); |
| }, 800); |
| } |
|
|
| function toggleTerminal() { |
| const modal = document.getElementById('terminal-modal'); |
| const input = document.getElementById('terminal-input'); |
| |
| if (modal.classList.contains('hidden')) { |
| modal.classList.remove('hidden'); |
| input.focus(); |
| } else { |
| modal.classList.add('hidden'); |
| } |
| } |
|
|
| function setupTerminal() { |
| const input = document.getElementById('terminal-input'); |
| const output = document.getElementById('terminal-output'); |
| |
| input.addEventListener('keypress', function (e) { |
| if (e.key === 'Enter') { |
| const command = input.value.trim(); |
| if (command) { |
| printToTerminal(`root@sys:~$ ${command}`); |
| executeCommand(command); |
| } |
| input.value = ''; |
| } |
| }); |
| } |
|
|
| function printToTerminal(text, color = 'text-green-400') { |
| const output = document.getElementById('terminal-output'); |
| const p = document.createElement('p'); |
| p.className = color + " font-mono"; |
| p.innerText = text; |
| output.appendChild(p); |
| output.scrollTop = output.scrollHeight; |
| } |
|
|
| async function executeCommand(cmd) { |
| const output = document.getElementById('terminal-output'); |
| const args = cmd.split(' '); |
| const command = args[0].toLowerCase(); |
| |
| |
| switch(command) { |
| case 'help': |
| case '?': |
| printToTerminal("CYBERSHIELD SOC v3.0 - Commandes disponibles:", "text-cyber-primary"); |
| printToTerminal(" netstat - Affiche les connexions actives"); |
| printToTerminal(" ps - Liste des processus suspects"); |
| printToTerminal(" kill [PID] - Termine un processus"); |
| printToTerminal(" block [IP] - Bloque une adresse IP au firewall"); |
| printToTerminal(" isolate - Mode confinement d'urgence"); |
| printToTerminal(" scan [target] - Scan de vulnérabilités rapide"); |
| printToTerminal(" clear - Nettoie le terminal"); |
| printToTerminal(" status - État des systèmes"); |
| break; |
| |
| case 'netstat': |
| printToTerminal("Connexions réseau actives:", "text-yellow-400"); |
| setTimeout(() => { |
| printToTerminal("tcp ESTABLISHED 192.168.1.100:443 -> 185.220.101.42:4444 [SUSPECT]", "text-red-400"); |
| printToTerminal("tcp ESTABLISHED 192.168.1.5:22 -> 10.0.0.15:22 [SSH]", "text-green-400"); |
| printToTerminal("udp OPEN 0.0.0.0:53 -> 8.8.8.8:53 [DNS]", "text-gray-400"); |
| printToTerminal("tcp LISTEN 0.0.0.0:8080 -> 0.0.0.0:0 [HTTP]", "text-gray-400"); |
| }, 300); |
| break; |
| |
| case 'ps': |
| printToTerminal("Processus en cours:", "text-yellow-400"); |
| setTimeout(() => { |
| printToTerminal("PID USER CPU MEM COMMAND", "text-gray-500"); |
| printToTerminal("1 root 0.1 0.2 /sbin/init", "text-gray-400"); |
| printToTerminal("1842 www-data 45.2 12.5 /usr/bin/python3 -m http.server [SUSPECT]", "text-red-400"); |
| printToTerminal("2156 mysql 2.1 8.4 mysqld --daemonize", "text-gray-400"); |
| printToTerminal("3421 root 0.0 0.1 /bin/bash /tmp/.hidden/script.sh [MALICIOUS]", "text-red-400 font-bold"); |
| }, 300); |
| break; |
| |
| case 'kill': |
| if (args[1]) { |
| printToTerminal(`Envoi du signal SIGTERM au processus ${args[1]}...`, "text-yellow-400"); |
| setTimeout(() => { |
| printToTerminal(`Processus ${args[1]} terminé.`, "text-green-400"); |
| addLogEntry(`Processus ${args[1]} terminé manuellement via terminal`, 'warning', 'ADMIN'); |
| }, 500); |
| } else { |
| printToTerminal("Usage: kill [PID]", "text-red-400"); |
| } |
| break; |
| |
| case 'block': |
| if (args[1]) { |
| const ip = args[1]; |
| if (/^(\d{1,3}\.){3}\d{1,3}$/.test(ip)) { |
| printToTerminal(`Blocage de l'IP ${ip}...`, "text-yellow-400"); |
| setTimeout(() => { |
| printToTerminal(`iptables -A INPUT -s ${ip} -j DROP`, "text-gray-500"); |
| printToTerminal(`IP ${ip} bloquée avec succès.`, "text-green-400"); |
| blockIP(ip); |
| }, 600); |
| } else { |
| printToTerminal("IP invalide.", "text-red-400"); |
| } |
| } else { |
| printToTerminal("Usage: block [IP]", "text-red-400"); |
| } |
| break; |
| |
| case 'isolate': |
| printToTerminal("INITIATION DU CONFINEMENT D'URGENCE...", "text-red-400 font-bold"); |
| setTimeout(() => { |
| printToTerminal("[-] Coupure des interfaces réseau externes...", "text-yellow-400"); |
| printToTerminal("[-] Isolation du VLAN sensibles...", "text-yellow-400"); |
| printToTerminal("[-] Preservation des logs forensiques...", "text-yellow-400"); |
| printToTerminal("[✓] SYSTÈME ISOLÉ - Mode quarantaine actif", "text-green-400 font-bold"); |
| isolateSystem(); |
| }, 1000); |
| break; |
| |
| case 'scan': |
| const target = args[1] || 'localhost'; |
| printToTerminal(`Lancement du scan de vulnérabilités sur ${target}...`, "text-yellow-400"); |
| setTimeout(() => { |
| printToTerminal("[*] Scan des ports ouverts...", "text-gray-500"); |
| printToTerminal("[!] Port 22/tcp open SSH - Version outdated", "text-orange-400"); |
| printToTerminal("[!] Port 3389/tcp open RDP - Vulnérable à BlueKeep", "text-red-400"); |
| printToTerminal("[*] Scan terminé. 2 vulnérabilités trouvées.", "text-green-400"); |
| }, 2000); |
| break; |
| |
| case 'clear': |
| output.innerHTML = ''; |
| break; |
| |
| case 'uptime': |
| const days = Math.floor(Math.random() * 100); |
| const hours = Math.floor(Math.random() * 24); |
| printToTerminal(`System uptime: ${days} days, ${hours} hours`, "text-gray-400"); |
| break; |
| |
| case 'status': |
| printToTerminal("Checking systems...", "text-yellow-400"); |
| setTimeout(() => { |
| printToTerminal("[✓] Firewall: ACTIVE", "text-green-400"); |
| printToTerminal("[✓] IDS/IPS: ONLINE", "text-green-400"); |
| printToTerminal("[✓] SIEM: CONNECTED", "text-green-400"); |
| printToTerminal("[✓] Threat Intel: FEED ACTIVE", "text-green-400"); |
| printToTerminal("[!] Backup: SYNCING...", "text-orange-400"); |
| }, 800); |
| break; |
| |
| default: |
| printToTerminal(`Command not found: ${cmd}. Type 'help' for available commands.`, "text-red-400"); |
| } |
| } |
|
|
| |
| function investigateLeak(leakType) { |
| if (!leakType) return; |
| addLogEntry(`Investigation lancée: ${leakType.toUpperCase()}`, 'info', 'DARKWEB'); |
| showToastAlert({ |
| type: 'DARKWEB_INVESTIGATION', |
| severity: 'HIGH', |
| description: `Analyse forensique de la fuite ${leakType} en cours...`, |
| timestamp: new Date().toISOString() |
| }); |
| } |
|
|
| |
| let packetCaptureActive = false; |
| function startPacketCapture() { |
| const container = document.getElementById('packet-analyzer'); |
| packetCaptureActive = !packetCaptureActive; |
| |
| if (packetCaptureActive) { |
| addLogEntry('Capture de paquets démarrée sur interface eth0', 'info', 'NETWORK'); |
| container.innerHTML = '<div class="text-green-400">Capture active...</div>'; |
| |
| const protocols = ['TCP', 'UDP', 'ICMP', 'DNS']; |
| const flags = ['SYN', 'ACK', 'PSH', 'FIN', 'RST']; |
| |
| const interval = setInterval(() => { |
| if (!packetCaptureActive) { |
| clearInterval(interval); |
| return; |
| } |
| |
| const proto = protocols[Math.floor(Math.random() * protocols.length)]; |
| const src = `192.168.1.${Math.floor(Math.random() * 255)}`; |
| const dst = Math.random() > 0.8 ? `185.220.${Math.floor(Math.random() * 255)}.${Math.floor(Math.random() * 255)}` : `10.0.0.${Math.floor(Math.random() * 50)}`; |
| const port = [4444, 5555, 80, 443, 22, 3389][Math.floor(Math.random() * 6)]; |
| const flag = flags[Math.floor(Math.random() * flags.length)]; |
| |
| const isSuspicious = dst.startsWith('185.220') || port === 4444; |
| const colorClass = isSuspicious ? 'text-red-400' : 'text-gray-400'; |
| const alert = isSuspicious ? ' [!SUSPICIOUS]' : ''; |
| |
| const line = document.createElement('div'); |
| line.className = `font-mono text-xs ${colorClass} border-l-2 ${isSuspicious ? 'border-red-500 bg-red-900/10' : 'border-gray-700'} pl-2 mb-1`; |
| line.innerHTML = `[${new Date().toLocaleTimeString()}] ${proto} ${src} -> ${dst}:${port} [${flag}]${alert}`; |
| |
| container.prepend(line); |
| if (container.children.length > 50) container.lastChild.remove(); |
| |
| }, 200); |
| } else { |
| addLogEntry('Capture de paquets arrêtée', 'info', 'NETWORK'); |
| container.innerHTML = '<div class="text-gray-500">Capture arrêtée. En attente...</div>'; |
| } |
| } |
|
|
| |
| function emergencyLockdown() { |
| if (!confirm('ÊTES-VOUS SÛR ? Cette action coupera TOUTES les connexions réseau et isolera le système.')) return; |
| |
| addLogEntry('🔒 CONFINEMENT D\'URGENCE ACTIVÉ PAR ADMINISTRATEUR', 'error', 'EMERGENCY'); |
| |
| |
| document.getElementById('status-eth').innerText = 'COUPÉ'; |
| document.getElementById('status-eth').className = 'text-red-500 font-mono animate-pulse'; |
| document.getElementById('icon-eth').classList.add('text-red-500'); |
| |
| document.getElementById('status-wifi').innerText = 'COUPÉ'; |
| document.getElementById('status-wifi').className = 'text-red-500 font-mono animate-pulse'; |
| document.getElementById('icon-wifi').classList.add('text-red-500'); |
| |
| |
| const modal = document.createElement('div'); |
| modal.className = 'fixed inset-0 bg-red-900/95 z-[200] flex items-center justify-center'; |
| modal.innerHTML = ` |
| <div class="text-center"> |
| <i data-feather="shield-off" class="w-24 h-24 text-white mx-auto mb-4 animate-pulse"></i> |
| <h2 class="text-4xl font-bold text-white mb-4">SYSTÈME EN CONFINEMENT</h2> |
| <p class="text-red-200 text-lg mb-8">Toutes les interfaces réseau ont été désactivées.</p> |
| <button onclick="this.closest('.fixed').remove()" class="bg-white text-red-900 px-8 py-3 rounded font-bold text-lg hover:bg-gray-200 transition"> |
| Acquitter |
| </button> |
| </div> |
| `; |
| document.body.appendChild(modal); |
| feather.replace(); |
| |
| playAlertSound(); |
| } |
|
|
| |
| function startMemoryAnalysis() { |
| addLogEntry('Analyse mémoire RAM démarrée...', 'info', 'FORENSICS'); |
| showToastAlert({ |
| type: 'FORENSICS_MEMORY', |
| severity: 'INFO', |
| description: 'Dump mémoire en cours - Recherche d\'artefacts malveillants', |
| timestamp: new Date().toISOString() |
| }); |
| |
| setTimeout(() => { |
| addLogEntry('Artefact détecté: Processus injecté dans lsass.exe', 'error', 'FORENSICS'); |
| }, 3000); |
| } |
|
|
| |
| function exportForensicReport() { |
| const report = { |
| case_id: 'FR-2025-0129-FICOBa', |
| date: new Date().toISOString(), |
| type: 'Breach Investigation', |
| iocs: ['185.220.101.42', 'ms-office-update.org', 'd41d8cd98f00b204e9800998ecf8427e'], |
| timeline: '28-29 Jan 2025', |
| analyst: 'CyberShield SOC' |
| }; |
| |
| const blob = new Blob([JSON.stringify(report, null, 2)], { type: 'application/json' }); |
| const url = URL.createObjectURL(blob); |
| const a = document.createElement('a'); |
| a.href = url; |
| a.download = `forensic_report_${report.case_id}.json`; |
| a.click(); |
| |
| addLogEntry(`Rapport forensique exporté: ${report.case_id}`, 'success', 'FORENSICS'); |
| } |
|
|
| |
| function addToBlocklist() { |
| const iocs = ['185.220.101.42', 'ms-office-update.org']; |
| iocs.forEach(ioc => { |
| if (ioc.includes('.')) { |
| addLogEntry(`IoC ajouté aux listes de blocage: ${ioc}`, 'success', 'THREAT_INTEL'); |
| } |
| }); |
| showToastAlert({ |
| type: 'IOC_BLOCKLIST', |
| severity: 'SUCCESS', |
| description: '3 indicateurs ajoutés aux listes de blocage', |
| timestamp: new Date().toISOString() |
| }); |
| } |
|
|
| |
| function initExfilChart() { |
| const canvas = document.getElementById('dataFlowCanvas'); |
| if (!canvas) return; |
| |
| const ctx = canvas.getContext('2d'); |
| if (!ctx) return; |
| |
| |
| canvas.width = canvas.offsetWidth || 400; |
| canvas.height = canvas.offsetHeight || 200; |
| |
| let dataPoints = []; |
| const maxPoints = 50; |
| |
| const normalEl = document.getElementById('normal-traffic'); |
| const suspiciousEl = document.getElementById('suspicious-outbound'); |
| const encryptedEl = document.getElementById('encrypted-ratio'); |
| |
| setInterval(() => { |
| |
| const normal = Math.random() * 2 + 1; |
| const suspicious = Math.random() > 0.7 ? Math.random() * 20 + 10 : 0; |
| |
| if (normalEl) normalEl.innerText = normal.toFixed(1) + ' MB/s'; |
| if (suspiciousEl) suspiciousEl.innerText = suspicious > 0 ? suspicious.toFixed(1) + ' MB/s' : '0 MB/s'; |
| if (encryptedEl) encryptedEl.innerText = Math.floor(Math.random() * 15 + 80) + '%'; |
| |
| dataPoints.push({ normal, suspicious }); |
| if (dataPoints.length > maxPoints) dataPoints.shift(); |
| |
| |
| ctx.fillStyle = '#0a0a12'; |
| ctx.fillRect(0, 0, canvas.width, canvas.height); |
| |
| if (dataPoints.length < 2) return; |
| |
| const step = canvas.width / maxPoints; |
| |
| |
| ctx.beginPath(); |
| ctx.strokeStyle = '#00ff9d'; |
| ctx.lineWidth = 2; |
| dataPoints.forEach((p, i) => { |
| const y = canvas.height - (p.normal / 25 * canvas.height); |
| if (i === 0) ctx.moveTo(0, y); |
| else ctx.lineTo(i * step, y); |
| }); |
| ctx.stroke(); |
| |
| |
| ctx.beginPath(); |
| ctx.strokeStyle = '#ef4444'; |
| ctx.lineWidth = 2; |
| dataPoints.forEach((p, i) => { |
| if (p.suspicious > 0) { |
| const y = canvas.height - (p.suspicious / 25 * canvas.height); |
| ctx.moveTo(i * step, canvas.height); |
| ctx.lineTo(i * step, y); |
| } |
| }); |
| ctx.stroke(); |
| |
| }, 1000); |
| } |
|
|
| |
| document.addEventListener('DOMContentLoaded', () => { |
| setTimeout(initExfilChart, 1000); |
| }); |
| |
| async function startVulnStream() { |
| const tableBody = document.getElementById('vuln-table-body'); |
| |
| const fetchVulns = async () => { |
| try { |
| const response = await fetch('http://localhost:3000/api/vulns'); |
| if (!response.ok) throw new Error('Server unavailable'); |
| const data = await response.json(); |
| |
| |
| const critEl = document.getElementById('vuln-critical-count'); |
| critEl.innerText = data.stats.critical; |
| if (data.stats.critical > 0) critEl.classList.add('text-red-500', 'animate-pulse'); |
| |
| document.getElementById('vuln-high-count').innerText = data.stats.high; |
| document.getElementById('vuln-med-count').innerText = data.stats.medium; |
|
|
| |
| tableBody.innerHTML = ''; |
| data.list.forEach(vuln => { |
| let color = 'text-blue-400'; |
| let badgeClass = 'bg-blue-500/20 text-blue-400 border-blue-500/30'; |
| |
| if(vuln.score >= 9) { |
| color = 'text-red-400 font-bold'; |
| badgeClass = 'bg-red-500/20 text-red-400 border-red-500/30 animate-pulse'; |
| } else if(vuln.score >= 7) { |
| color = 'text-orange-400'; |
| badgeClass = 'bg-orange-500/20 text-orange-400 border-orange-500/30'; |
| } |
|
|
| const row = document.createElement('tr'); |
| row.className = 'hover:bg-gray-800/30 transition border-b border-gray-800/50'; |
| row.innerHTML = ` |
| <td class="px-6 py-3 font-mono text-white">${vuln.id}</td> |
| <td class="px-6 py-3">${vuln.component}</td> |
| <td class="px-6 py-3 ${color}">${vuln.score}</td> |
| <td class="px-6 py-3"> |
| <span class="px-2 py-1 rounded text-xs border ${badgeClass}">${vuln.status}</span> |
| </td> |
| <td class="px-6 py-3"> |
| <button onclick="applyPatch('${vuln.id}')" class="text-cyber-primary hover:text-white hover:bg-cyber-primary/20 px-2 py-1 rounded transition text-xs border border-cyber-primary/30"> |
| PATCH |
| </button> |
| </td> |
| `; |
| tableBody.appendChild(row); |
| }); |
| feather.replace(); |
| } catch(e) { |
| console.error('Erreur vulnérabilités:', e); |
| } |
| }; |
| |
| fetchVulns(); |
| setInterval(fetchVulns, 10000); |
| } |
|
|
| |
| async function applyPatch(cveId) { |
| if (!confirm(`Confirmer l'application du patch pour ${cveId} ?`)) return; |
| |
| addLogEntry(`Application du patch ${cveId} en cours...`, 'info', 'ADMIN'); |
| |
| try { |
| |
| const response = await fetch('http://localhost:3000/api/patch', { |
| method: 'POST', |
| headers: { 'Content-Type': 'application/json' }, |
| body: JSON.stringify({ cve: cveId }) |
| }); |
| |
| if (response.ok) { |
| addLogEntry(`Patch ${cveId} appliqué avec succès`, 'success', 'SYSTEM'); |
| playBeep(800, 'sine', 0.2); |
| } |
| } catch (e) { |
| addLogEntry(`Échec application patch ${cveId}`, 'error', 'SYSTEM'); |
| } |
| } |
| |
|
|
| function initCognitiveEngine() { |
| const chartContainer = document.getElementById('predictionChart'); |
| const barCount = 30; |
| for(let i = 0; i < barCount; i++) { |
| const bar = document.createElement('div'); |
| bar.className = 'bg-purple-500/40 w-2 rounded-t-sm transition-all duration-500'; |
| |
| let h = 20 + (i * 2) + (Math.random() * 10 - 5); |
| if (h > 90) h = 90; |
| bar.style.height = `${h}%`; |
| |
| |
| if (Math.random() > 0.95) { |
| bar.classList.add('bg-red-500', 'shadow-[0_0_10px_rgba(239,68,68,0.8)]'); |
| } |
| |
| chartContainer.appendChild(bar); |
| } |
| } |
|
|
| function startCognitiveStream() { |
| const container = document.getElementById('cognitive-log'); |
| |
| const insights = [ |
| { type: 'WARNING', text: 'Anomalie de trafic détectée: Protocol SSH handshake failed.', score: '92%' }, |
| { type: 'INFO', text: 'Mise à jour des signatures de pare-feu terminée.', score: '100%' }, |
| { type: 'ANALYSIS', text: 'Pattern reconnu: Attaque DDoS de faible niveau (Type UDP Flood).', score: '88%' }, |
| { type: 'OPTIMIZATION', text: 'Optimisation dynamique: Cache Redis activé sur Cluster A.', score: 'N/A' }, |
| { type: 'DETECTION', text: 'Compte utilisateur "admin" connecté depuis une localisation inhabituelle.', score: '45%' } |
| ]; |
|
|
| setInterval(() => { |
| |
| const item = insights[Math.floor(Math.random() * insights.length)]; |
| |
| const div = document.createElement('div'); |
| div.className = "border-b border-gray-800 pb-2 animate-fade-in"; |
| |
| let color = "text-gray-400"; |
| if(item.type === 'WARNING') color = "text-red-400"; |
| if(item.type === 'OPTIMIZATION') color = "text-green-400"; |
|
|
| div.innerHTML = ` |
| <div class="flex justify-between"> |
| <span class="${color} font-bold">[${item.type}]</span> |
| <span class="text-gray-600 font-mono">${item.score}</span> |
| </div> |
| <p class="text-gray-300 mt-1">${item.text}</p> |
| `; |
| |
| container.prepend(div); |
| if(container.children.length > 10) container.lastChild.remove(); |
| }, 5000); |
| } |
|
|
| function generateCognitiveInsight() { |
| const container = document.getElementById('cognitive-log'); |
| const div = document.createElement('div'); |
| div.className = "border-l-2 border-purple-500 pl-3 py-1 bg-purple-900/10 animate-pulse"; |
| div.innerHTML = ` |
| <span class="text-purple-400 text-xs font-bold uppercase">Re-Analyse Terminée</span> |
| <p class="text-white text-sm mt-1">Aucun risque critique identifié. Efficacité du système: 99.8%</p> |
| `; |
| container.prepend(div); |
| } |
|
|
| |
| async function startInfraStream() { |
| const container = document.getElementById('server-rack-container'); |
| |
| const fetchInfra = async () => { |
| try { |
| const response = await fetch('http://localhost:3000/api/infra'); |
| if (!response.ok) throw new Error('Network response was not ok'); |
| const data = await response.json(); |
|
|
| if(container.children.length === 0) { |
| data.servers.forEach(srv => { |
| const card = document.createElement('div'); |
| card.className = 'server-card p-4 rounded-xl shadow-lg relative'; |
| card.id = `srv-${srv.id}`; |
| container.appendChild(card); |
| }); |
| } |
|
|
| data.servers.forEach(srv => { |
| const card = document.getElementById(`srv-${srv.id}`); |
| if(card) { |
| const loadColor = srv.load > 80 ? 'bg-red-500 shadow-[0_0_10px_rgba(239,68,68,0.5)]' : |
| (srv.load > 50 ? 'bg-yellow-400' : 'bg-cyber-secondary'); |
| |
| const tempColor = srv.temp > 70 ? 'text-red-400 animate-pulse' : |
| (srv.temp > 60 ? 'text-yellow-400' : 'text-white'); |
| |
| |
| const statusBadge = srv.status === 'Online' |
| ? '<span class="flex items-center text-green-400"><span class="w-1.5 h-1.5 bg-green-400 rounded-full mr-1 animate-pulse"></span>Online</span>' |
| : '<span class="flex items-center text-red-400"><span class="w-1.5 h-1.5 bg-red-500 rounded-full mr-1"></span>Warning</span>'; |
| |
| card.innerHTML = ` |
| <div class="flex justify-between items-center mb-4"> |
| <div class="flex items-center"> |
| <i data-feather="server" class="w-4 h-4 text-gray-400 mr-2"></i> |
| <h4 class="font-bold text-white text-sm">${srv.name}</h4> |
| </div> |
| <span class="text-xs">${statusBadge}</span> |
| </div> |
| <div class="space-y-3"> |
| <div> |
| <div class="flex justify-between text-xs text-gray-400 mb-1"> |
| <span>CPU Load</span> |
| <span class="font-mono ${srv.load > 80 ? 'text-red-400' : ''}">${srv.load}%</span> |
| </div> |
| <div class="w-full bg-gray-800 rounded-full h-1.5 overflow-hidden"> |
| <div class="${loadColor} h-1.5 rounded-full transition-all duration-700" style="width: ${Math.min(srv.load, 100)}%"></div> |
| </div> |
| </div> |
| <div class="grid grid-cols-3 gap-2 mt-4"> |
| <div class="bg-gray-900/50 p-2 rounded border border-gray-800"> |
| <p class="text-[10px] text-gray-500 uppercase">RAM</p> |
| <p class="text-xs font-mono text-white">${srv.ram} GB</p> |
| </div> |
| <div class="bg-gray-900/50 p-2 rounded border border-gray-800"> |
| <p class="text-[10px] text-gray-500 uppercase">Temp</p> |
| <p class="text-xs font-mono ${tempColor}">${srv.temp}°C</p> |
| </div> |
| <div class="bg-gray-900/50 p-2 rounded border border-gray-800"> |
| <p class="text-[10px] text-gray-500 uppercase">Proc</p> |
| <p class="text-xs font-mono text-white">${srv.processes}</p> |
| </div> |
| </div> |
| ${srv.load > 80 ? '<div class="mt-2 text-[10px] text-red-400 border border-red-500/30 bg-red-500/10 px-2 py-1 rounded text-center">SURCHARGE CPU DETECTÉE</div>' : ''} |
| </div> |
| `; |
| } |
| }); |
| feather.replace(); |
| } catch(e) { |
| console.error('Erreur infrastructure:', e); |
| } |
| }; |
| |
| fetchInfra(); |
| setInterval(fetchInfra, 3000); |
| } |
|
|