+
Sécurité
+
+
+
+
Double authentification (2FA)
TOTP activé · App Authenticator
+
+
+
+
+
Biométrie
Empreinte / Face ID
+
+
+
+
+
Code PIN
Modifier le code PIN de secours
+
›
+
-
-
-
-
+
+function selectMFA(btn, method) {
+ document.querySelectorAll('.mfa-method-btn').forEach(b => b.classList.remove('active'));
+ btn.classList.add('active');
+ if(method === 'sms') showToast('SMS envoyé au +33 6 ** ** ** 42');
+ if(method === 'email') showToast('Email envoyé à a.dupont@****');
+}
+
+function verifyMFA() {
+ const inputs = document.querySelectorAll('.mfa-digit');
+ let code = '';
+ inputs.forEach(i => code += i.value);
+ if(code.length < 6) { showToast('Entrez les 6 chiffres'); return; }
+ if(code === currentCode || code === correctCode) {
+ if(totpInterval) clearInterval(totpInterval);
+ showToast('✓ Authentification réussie !');
+ setTimeout(() => {
+ goTo('screen-app');
+ renderQR();
+ renderBadgeList();
+ renderHistory();
+ renderDashboard();
+ }, 800);
+ } else {
+ showToast('❌ Code incorrect, réessayez');
+ buildMFAInputs();
+ }
+}
+
+// ===== QR CODE =====
+function renderQR() {
+ const canvas = document.getElementById('qr-canvas');
+ const ctx = canvas.getContext('2d');
+ const size = 154;
+ const data = 'SAFEACCESS:AC-0012:' + Date.now();
+ drawQR(ctx, data, size);
+ setInterval(() => {
+ const newData = 'SAFEACCESS:AC-0012:' + Date.now();
+ drawQR(ctx, newData, size);
+ }, 60000);
+}
+
+function drawQR(ctx, data, size) {
+ ctx.clearRect(0, 0, size, size);
+ ctx.fillStyle = '#fff';
+ ctx.fillRect(0, 0, size, size);
+ const cells = 25;
+ const cellSize = Math.floor(size / cells);
+ const hash = Array.from(data).reduce((a, c) => ((a << 5) - a + c.charCodeAt(0)) | 0, 0);
+ for(let r = 0; r < cells; r++) {
+ for(let c = 0; c < cells; c++) {
+ const seed = (r * 31 + c * 17 + hash + r*c) % 100;
+ const shouldFill = seed < 45 || (r < 7 && c < 7) || (r < 7 && c > cells-8) || (r > cells-8 && c < 7);
+ if(shouldFill) {
+ ctx.fillStyle = '#0D2B5E';
+ ctx.fillRect(c * cellSize + 1, r * cellSize + 1, cellSize - 1, cellSize - 1);
+ }
+ }
+ }
+ // Finder patterns
+ [[0,0],[0,cells-7],[cells-7,0]].forEach(([row,col]) => {
+ ctx.strokeStyle = '#0D2B5E'; ctx.lineWidth = 2;
+ ctx.strokeRect(col*cellSize+1, row*cellSize+1, 6*cellSize, 6*cellSize);
+ ctx.fillStyle = '#0D2B5E';
+ ctx.fillRect((col+2)*cellSize, (row+2)*cellSize, 3*cellSize, 3*cellSize);
+ });
+}
+
+// ===== NFC SIMULATION =====
+function simulateNFC() {
+ const ring = document.getElementById('nfc-ring');
+ const status = document.getElementById('nfc-status-text');
+ ring.style.borderColor = '#FBC02D';
+ ring.style.animation = 'nfc-pulse 0.8s ease-in-out infinite';
+ status.textContent = 'Lecture NFC en cours...';
+ status.style.color = '#E65100';
+ setTimeout(() => {
+ ring.style.borderColor = '#4CAF50';
+ ring.style.animation = 'none';
+ status.textContent = '✓ Accès autorisé — Bureau Principal';
+ status.style.color = '#2E7D32';
+ showToast('✓ Accès autorisé — Bureau Principal');
+ setTimeout(() => {
+ ring.style.borderColor = 'var(--blue-light)';
+ ring.style.animation = 'nfc-pulse 2.5s ease-in-out infinite';
+ status.textContent = 'En attente d\'un lecteur NFC';
+ status.style.color = 'var(--blue-main)';
+ }, 3000);
+ }, 1800);
+}
+
+// ===== BADGE LIST =====
+const techIcons = {
+ nfc: `
`,
+ rfid: `
`,
+ qr: `
`,
+ bt: `
`,
+};
+
+function renderBadgeList() {
+ const list = document.getElementById('badge-list');
+ list.innerHTML = badges.map(b => `
+
+
${techIcons[b.tech]}
+
+
${b.name}
+
${b.uid} · ${b.zones.join(', ')}
+
+
+
${b.tech.toUpperCase()}
+
+
+
+ `).join('');
+}
+
+// ===== HISTORY =====
+const inIcon = `
`;
+const outIcon = `
`;
+const refIcon = `
`;
+
+function renderHistory(filter = '') {
+ const list = document.getElementById('history-list');
+ const filtered = history.filter(h => !filter || h.loc.toLowerCase().includes(filter.toLowerCase()));
+ let html = '';
+ let lastDate = '';
+ filtered.forEach(h => {
+ if(h.date !== lastDate) {
+ html += `
${h.date}
`;
+ lastDate = h.date;
+ }
+ const iconType = h.status === 'refused' ? 'refused' : h.type === 'in' ? 'in' : 'out';
+ const icon = h.status === 'refused' ? refIcon : h.type === 'in' ? inIcon : outIcon;
+ const typeLabel = h.type === 'in' ? 'Entrée' : 'Sortie';
+ html += `
+
+
${icon}
+
+
${typeLabel} · ${h.loc}
+
${h.sub}
+
+
+
${h.status === 'ok' ? 'AUTORISÉ' : 'REFUSÉ'}
+
${h.date.split(',')[1] || ''}
+
+
`;
+ });
+ list.innerHTML = html || '
Aucun résultat
';
+}
+
+document.getElementById('history-search').addEventListener('input', e => renderHistory(e.target.value));
+
+// ===== DASHBOARD =====
+function renderDashboard() {
+ const zl = document.getElementById('zone-list');
+ zl.innerHTML = zones.map(z => `
+
+
+
${z.risk === 'high' ? 'Restreint' : z.risk === 'medium' ? 'Modéré' : 'Public'}
+
`).join('');
+
+ const dl = document.getElementById('dash-logs');
+ dl.innerHTML = history.slice(0,4).map(h => `
+
+
${h.status === 'refused' ? refIcon : h.type === 'in' ? inIcon : outIcon}
+
+
${h.status === 'ok' ? 'OK' : 'REFUSÉ'}
+
`).join('');
+}
+
+// ===== ADD BADGE MODAL =====
+function showAddBadgeModal() {
+ document.getElementById('modal-add').classList.add('open');
+}
+function closeModal() {
+ document.getElementById('modal-add').classList.remove('open');
+}
+function addBadge(tech) {
+ closeModal();
+ const newBadge = {
+ id: 'B00' + (badges.length + 1),
+ name: 'Nouveau badge ' + tech,
+ tech: tech.toLowerCase() === 'nfc' ? 'nfc' : tech.toLowerCase() === 'rfid' ? 'rfid' : tech.toLowerCase() === 'qr code' ? 'qr' : 'bt',
+ uid: 'NEW-' + Math.random().toString(36).substr(2,8).toUpperCase(),
+ zones: ['Bureau Principal'],
+ active: true,
+ };
+ badges.push(newBadge);
+ if(currentTab === 'mes-badges') renderBadgeList();
+ showToast('✓ Badge ' + tech + ' ajouté');
+}
+
+// ===== TOAST =====
+function showToast(msg) {
+ const t = document.getElementById('toast');
+ t.textContent = msg;
+ t.classList.add('show');
+ setTimeout(() => t.classList.remove('show'), 2500);
+}
+
+// ===== INIT =====
+document.getElementById('fab-btn').style.display = 'none';
+
diff --git a/app/manifest.json b/app/manifest.json
new file mode 100644
index 0000000..3bf5d84
--- /dev/null
+++ b/app/manifest.json
@@ -0,0 +1,17 @@
+{
+ "name": "SafeAccess — Gestion de badges",
+ "short_name": "SafeAccess",
+ "description": "Gestion de badges d'accès NFC, RFID, QR Code et Bluetooth avec 2FA",
+ "start_url": "/index.html",
+ "display": "standalone",
+ "background_color": "#0D2B5E",
+ "theme_color": "#1565C0",
+ "orientation": "portrait",
+ "icons": [
+ { "src": "icons/icon-192.png", "sizes": "192x192", "type": "image/png", "purpose": "any maskable" },
+ { "src": "icons/icon-512.png", "sizes": "512x512", "type": "image/png", "purpose": "any maskable" }
+ ],
+ "categories": ["security", "utilities", "business"],
+ "lang": "fr",
+ "scope": "/"
+}
diff --git a/app/sw.js b/app/sw.js
new file mode 100644
index 0000000..efd1f5c
--- /dev/null
+++ b/app/sw.js
@@ -0,0 +1,12 @@
+const CACHE = 'safeaccess-v1';
+const ASSETS = ['/', '/index.html', '/manifest.json'];
+
+self.addEventListener('install', e => {
+ e.waitUntil(caches.open(CACHE).then(c => c.addAll(ASSETS)));
+});
+
+self.addEventListener('fetch', e => {
+ e.respondWith(
+ caches.match(e.request).then(r => r || fetch(e.request)).catch(() => caches.match('/index.html'))
+ );
+});
diff --git a/docker-compose.yml b/docker-compose.yml
index a7a3910..07a481e 100755
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -1,20 +1,9 @@
services:
- backend:
- build:
- context: .
- dockerfile: Dockerfile
- container_name: cyberusgate_backend
- ports:
- - "8000:8000"
- environment:
- - PYTHONUNBUFFERED=1
- networks:
- - cyberusgate-network
frontend:
build:
context: .
- dockerfile: Dockerfile.nginx
+ dockerfile: Dockerfile
container_name: cyberusgate_frontend
ports:
- "8888:80"
diff --git a/old/Dockerfile b/old/Dockerfile
new file mode 100644
index 0000000..92bc5bc
--- /dev/null
+++ b/old/Dockerfile
@@ -0,0 +1,7 @@
+FROM python:3.9-slim
+WORKDIR /app
+COPY app/requirements.txt .
+RUN pip install --no-cache-dir -r requirements.txt
+COPY app/ .
+EXPOSE 8000
+CMD ["python", "-m", "uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
\ No newline at end of file
diff --git a/Dockerfile.nginx b/old/Dockerfile.nginx
similarity index 100%
rename from Dockerfile.nginx
rename to old/Dockerfile.nginx
diff --git a/old/app/apple-touch-icon.png b/old/app/apple-touch-icon.png
new file mode 100644
index 0000000..f319dce
Binary files /dev/null and b/old/app/apple-touch-icon.png differ
diff --git a/old/app/favicon-16x16.png b/old/app/favicon-16x16.png
new file mode 100644
index 0000000..5e7b212
Binary files /dev/null and b/old/app/favicon-16x16.png differ
diff --git a/old/app/favicon-32x32.png b/old/app/favicon-32x32.png
new file mode 100644
index 0000000..2d77783
Binary files /dev/null and b/old/app/favicon-32x32.png differ
diff --git a/old/app/index.html b/old/app/index.html
new file mode 100644
index 0000000..6775709
--- /dev/null
+++ b/old/app/index.html
@@ -0,0 +1,265 @@
+
+
+
+
+
+
Cyberusgate - Démonstration
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Utilisateurs Actifs
+
+
+
+
+
+
+ | Nom |
+ Rôle |
+ Statut |
+ Action |
+
+
+
+
+
+
+
+
+
+
+
+
Journal d'Événements en Temps Réel
+
+
+
+
+
+
+
+
+
+
+
+
Simulateur de Porte
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/main.py b/old/app/main.py
old mode 100755
new mode 100644
similarity index 100%
rename from app/main.py
rename to old/app/main.py
diff --git a/app/requirements.txt b/old/app/requirements.txt
old mode 100755
new mode 100644
similarity index 100%
rename from app/requirements.txt
rename to old/app/requirements.txt
diff --git a/old/app/site.webmanifest b/old/app/site.webmanifest
new file mode 100644
index 0000000..45dc8a2
--- /dev/null
+++ b/old/app/site.webmanifest
@@ -0,0 +1 @@
+{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}
\ No newline at end of file
diff --git a/old/docker-compose.yml b/old/docker-compose.yml
new file mode 100644
index 0000000..a7a3910
--- /dev/null
+++ b/old/docker-compose.yml
@@ -0,0 +1,26 @@
+services:
+ backend:
+ build:
+ context: .
+ dockerfile: Dockerfile
+ container_name: cyberusgate_backend
+ ports:
+ - "8000:8000"
+ environment:
+ - PYTHONUNBUFFERED=1
+ networks:
+ - cyberusgate-network
+
+ frontend:
+ build:
+ context: .
+ dockerfile: Dockerfile.nginx
+ container_name: cyberusgate_frontend
+ ports:
+ - "8888:80"
+ networks:
+ - cyberusgate-network
+
+networks:
+ cyberusgate-network:
+ driver: bridge
\ No newline at end of file