Code index php
Interface de Contrôle : index.php
Ce fichier est le cœur de votre interface. Il affiche le statut de la caméra, permet le contrôle manuel via un code PIN sécurisé et affiche l'historique des 10 dernières actions détectées.
1. Deux possibilités pour créer le fichier.
Vous créez le fichier index.php sur votre PC et tranferrez le via FTP
Ou depuis votre terminal, connectez-vous à votre Raspberry Pi et créez le fichier index.php :
sudo nano /var/www/html/index.php Copiez l'intégralité du code ci-dessous et collez-le dans l'éditeur :
<?php
session_start();
date_default_timezone_set('Europe/Paris');
$config = include('/var/www/html/data/.env.php');
define('PIN_HASH_STORAGE', $config['PIN_HASH']);
define('BLACKLIST_FILE', $config['DATA_DIR'] . 'ip_blacklist.json');
define('LOG_FILE', $config['LOG_PATH']);
define('DEVICES_FILE', $config['DATA_DIR'] . 'devices_status.json');
// On récupère la liste des noms depuis la config
$allowed_devices = $config['DEVICES_LIST'] ?? ['Sylviane', 'Daniel'];
const MAX_ATTEMPTS = 3;
function writeLog($source, $message, $details = "-") {
$date = date('Y-m-d H:i:s');
$line = "[$date] [$source] $message: $details\n";
file_put_contents(LOG_FILE, $line, FILE_APPEND | LOCK_EX);
$lines = file(LOG_FILE);
if (count($lines) > 500) {
file_put_contents(LOG_FILE, implode("", array_slice($lines, -500)), LOCK_EX);
}
}
if (isset($_GET['get_data'])) {
header('Content-Type: application/json');
$devices = file_exists(DEVICES_FILE) ? json_decode(file_get_contents(DEVICES_FILE), true) : [];
$is_active = (trim(shell_exec("systemctl is-active motioneye")) === 'active');
$history = [];
if (file_exists(LOG_FILE)) {
$lines = array_reverse(array_slice(file(LOG_FILE), -15));
foreach ($lines as $line) {
preg_match('/\[(.*?)\] \[(.*?)\] (.*?)(?:: (.*))?$/', $line, $m);
$history[] = [
'date' => $m[1] ?? "-",
'source' => strtoupper($m[2] ?? "INFO"),
'message' => trim($m[3] ?? $line),
'details' => trim($m[4] ?? "-")
];
}
}
echo json_encode(['devices' => $devices, 'history' => $history, 'service_active' => $is_active]);
exit;
}
$client_ip = $_SERVER['REMOTE_ADDR'];
if (file_exists(BLACKLIST_FILE)) {
$blacklist = json_decode(file_get_contents(BLACKLIST_FILE), true) ?: [];
if (isset($blacklist[$client_ip])) {
writeLog("SECURITY", "Accès refusé (IP bannie)", $client_ip);
exit("<body style='font-family:sans-serif;text-align:center;padding-top:100px;background:#f2f2f7;'><div style='background:white;display:inline-block;padding:40px;border-radius:20px;box-shadow:0 10px 30px rgba(0,0,0,0.1);'><h1 style='color:#ff3b30;'>❌ ACCÈS BLOQUÉ</h1></div></body>");
}
}
$login_error = ""; // Initialisation du message d'erreur
if (isset($_POST['pin'])) {
if (hash('sha256', $_POST['pin']) === PIN_HASH_STORAGE) {
$_SESSION['pin_ok'] = true;
$_SESSION['pin_attempts'] = 0;
header("Location: index.php"); exit;
} else {
$_SESSION['pin_attempts'] = ($_SESSION['pin_attempts'] ?? 0) + 1;
$remaining = MAX_ATTEMPTS - $_SESSION['pin_attempts'];
if ($_SESSION['pin_attempts'] >= MAX_ATTEMPTS) {
$blacklist = file_exists(BLACKLIST_FILE) ? json_decode(file_get_contents(BLACKLIST_FILE), true) : [];
$blacklist[$client_ip] = ['blocked_time' => date('Y-m-d H:i:s'), 'reason' => 'Échecs PIN Dashboard'];
file_put_contents(BLACKLIST_FILE, json_encode($blacklist, JSON_PRETTY_PRINT));
writeLog("SECURITY", "IP bannie (Trop d'échecs PIN)", $client_ip);
// Envoi mail si activé... (votre code mail reste identique)
header("Location: index.php"); exit;
} else {
// Message d'erreur avec compte à rebours
$login_error = "Code incorrect. Tentatives restantes : <strong>$remaining</strong>";
}
}
}
if (isset($_GET['logout'])) { session_destroy(); header("Location: index.php"); exit; }
$is_active = (trim(shell_exec("systemctl is-active motioneye")) === 'active');
?>
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Alarme Maison</title>
<style>
body { font-family: -apple-system, system-ui, sans-serif; background: #f2f2f7; margin: 0; padding: 15px; color: #1c1c1e; }
.container { max-width: 950px; margin: 0 auto; text-align: center; }
.card { background: white; padding: 25px; border-radius: 20px; box-shadow: 0 10px 30px rgba(0,0,0,0.05); margin-bottom: 20px; }
.presence-wrapper { display: flex; justify-content: center; gap: 10px; margin-bottom: 20px; flex-wrap: wrap; }
.badge { padding: 8px 15px; border-radius: 12px; font-weight: 700; font-size: 13px; display: flex; align-items: center; gap: 5px; border: 2px solid; text-transform: uppercase; }
.is-present { border-color: #34c759; color: #248a3d; background: #f2faf4; }
.is-absent { border-color: #ff3b30; color: #d70015; background: #fff2f2; }
.btn { padding: 20px; font-size: 18px; color: white; border: none; border-radius: 14px; cursor: pointer; width: 100%; margin: 10px 0; font-weight: 700; text-transform: uppercase; }
.btn-green { background: #34c759; } .btn-red { background: #ff3b30; }
.table-container { background: white; border-radius: 20px; padding: 15px; overflow-x: auto; box-shadow: 0 10px 30px rgba(0,0,0,0.05); }
table { width: 100%; border-collapse: collapse; font-size: 14px; min-width: 800px; }
th { background: #f8f8fa; padding: 15px; text-align: left; font-weight: 700; border-bottom: 2px solid #efeff4; }
td { padding: 15px; border-top: 1px solid #f2f2f7; text-align: left; }
.source-tag { background: #f2f2f7; padding: 6px 12px; border-radius: 10px; font-weight: 800; font-size: 11px; display: inline-flex; align-items: center; gap: 5px; }
.color-action-green { color: #248a3d; font-weight: 800; }
.color-action-red { color: #d70015; font-weight: 800; }
.color-info-blue { color: #007aff; font-weight: 700; }
.live-dot { height: 8px; width: 8px; background-color: #34c759; border-radius: 50%; display: inline-block; margin-right: 5px; animation: blink 1.5s infinite; }
@keyframes blink { 0% { opacity: 1; } 50% { opacity: 0.3; } 100% { opacity: 1; } }
.pin-input { width: 140px; padding: 15px; font-size: 32px; text-align: center; border-radius: 16px; border: 3px solid #007aff; }
</style>
</head>
<body>
<div class="container">
<h1 style="font-weight: 800; margin-bottom: 25px;">Alarme Maison</h1>
<?php if (!isset($_SESSION['pin_ok'])): ?>
<div class="card">
<h2>Code PIN Requis</h2>
<?php if ($login_error): ?>
<div style="color: #ff3b30; margin-bottom: 15px; background: #fff2f2; padding: 10px; border-radius: 10px; border: 1px solid #ff3b30;">
<?php echo $login_error; ?>
</div>
<?php endif; ?>
<form method="post" autocomplete="off">
<input type="password" name="pin" maxlength="4" class="pin-input" inputmode="numeric" autofocus oninput="if(this.value.length===4)this.form.submit();">
</form>
<p style="font-size: 0.8em; color: #8e8e93; margin-top: 10px;">L'IP sera bannie après <?php echo MAX_ATTEMPTS; ?> échecs.</p>
</div>
<?php else: ?>
<div class="card">
<div id="service-status" style="font-size: 24px; font-weight: 800; color: <?php echo $is_active ? '#248a3d' : '#d70015'; ?>">
<?php echo $is_active ? 'SERVICE ACTIF' : 'SERVICE INACTIF'; ?>
</div>
</div>
<div class="presence-wrapper">
<?php foreach($allowed_devices as $name): ?>
<div id="badge-<?php echo $name; ?>" class="badge">📱 <?php echo $name; ?> : <span id="txt-<?php echo $name; ?>">...</span></div>
<?php endforeach; ?>
</div>
<div class="card">
<button class="btn btn-green" onclick="runAction('start')">Activer la Caméra (ON)</button>
<button class="btn btn-red" onclick="runAction('stop')">Arrêter la Caméra (OFF)</button>
<div id="status-container" style="margin-top:12px; font-weight:600; color:#8e8e93; display: flex; align-items: center; justify-content: center;">
<span class="live-dot"></span> Vérification : <span id="last-check" style="margin-left:5px;"><?php echo date('H:i:s'); ?></span>
</div>
</div>
<div class="table-container">
<h3>Historique des dernières activités</h3>
<table>
<thead><tr><th>Date & Heure</th><th>Origine</th><th>Action / Événement</th><th>Détails</th></tr></thead>
<tbody id="history-body"></tbody>
</table>
</div>
<p style="margin-top:30px;"><a href="?logout=1" style="color:#8e8e93; font-weight:600; text-decoration:none; font-size:13px;">Fermer la session</a></p>
<?php endif; ?>
</div>
<script>
// On passe la liste des noms autorisés au Javascript
const allowedDevices = <?php echo json_encode($allowed_devices); ?>;
function refreshAll() {
fetch('index.php?get_data=1').then(r => r.json()).then(data => {
const now = new Date();
const timeStr = now.getHours().toString().padStart(2, '0') + ":" + now.getMinutes().toString().padStart(2, '0') + ":" + now.getSeconds().toString().padStart(2, '0');
document.getElementById('last-check').textContent = timeStr;
const statusTitle = document.getElementById('service-status');
if (data.service_active) { statusTitle.textContent = 'SERVICE ACTIF'; statusTitle.style.color = '#248a3d'; }
else { statusTitle.textContent = 'SERVICE INACTIF'; statusTitle.style.color = '#d70015'; }
// MISE À JOUR DYNAMIQUE DES BADGES
allowedDevices.forEach(name => {
const status = data.devices[name] || 'inconnu';
const badge = document.getElementById('badge-' + name);
const txt = document.getElementById('txt-' + name);
if(badge && txt) {
txt.textContent = status;
badge.className = 'badge ' + (status.toLowerCase() === 'present' ? 'is-present' : 'is-absent');
}
});
const tbody = document.getElementById('history-body');
tbody.innerHTML = '';
data.history.forEach(log => {
const icon = (log.source === "MANUAL") ? "📱" : (log.source === "AUTO" ? "🤖" : (log.source === "SECURITY" ? "🛡️" : "ℹ️"));
let msgClass = "color-info-blue";
if (log.source === "MANUAL" || log.source === "AUTO") {
if (!log.message.includes("reste") && !log.message.includes("inchangé")) {
if (log.message.includes('DÉMARRÉE') || log.message.includes('ON')) msgClass = "color-action-green";
else if (log.message.includes('ARRÊTÉE') || log.message.includes('OFF')) msgClass = "color-action-red";
}
} else if (log.source === "SECURITY") {
if (log.message.includes("bannie") || log.message.includes("refusé")) msgClass = "color-action-red";
}
tbody.innerHTML += `<tr><td style="color:#636366;">${log.date}</td><td><span class="source-tag"><span>${icon}</span> ${log.source}</span></td><td class="${msgClass}">${log.message}</td><td style="color: #3a3a3c; font-size:13px;">${log.details}</td></tr>`;
});
});
}
setInterval(refreshAll, 5000); refreshAll();
function runAction(action) { fetch('secure_action.php', { method: 'POST', body: new URLSearchParams({'action': action}) }).then(() => { setTimeout(refreshAll, 1000); }); }
</script>
</body>
</html>2. Sécurisation par Code PIN
Pour que le système fonctionne, vous devez remplacer la valeur PIN_HASH_STORAGE par le hash de votre code secret.
Générer votre Hash (exemple pour le code 1234) :
echo -n "1234" | sha256sum Le résultat sera une suite de caractères comme :
03ac674216f3e15c761ee1a5e255f067953623c8b388b4459e13f978d7c846f4