<?php
// Page déplacée depuis ancien index.php (liste & actions backups)
session_start();
@date_default_timezone_set('Africa/Casablanca');
require_once __DIR__.'/config/connexion.php';
require_once __DIR__.'/config/auth.php';
gc_require_login();

// Connexion principale déjà fournie par config/connexion.php via $gc_pdo
$FTP_HOST=''; $FTP_USER=''; $FTP_PASS=''; $FTP_PORT=21; $FTP_BASE_DIR=''; // pas de dossier par défaut, toujours depuis DB
$FTP_PASSIVE=1; $FTP_SSL=0; $FTP_ACTIVE=1; // nouveaux champs
// Charger configuration dynamique si disponible
try {
	if(isset($gc_pdo) && $gc_pdo instanceof PDO){
		$stF = $gc_pdo->query("SELECT ftp_host,ftp_port,ftp_username,ftp_password,ftp_base_dir,passive,ssl_enabled,active FROM ftp_settings_table WHERE id=1 LIMIT 1");
		if($cfg = $stF->fetch(PDO::FETCH_ASSOC)){
			if(!empty($cfg['ftp_host'])) $FTP_HOST = trim($cfg['ftp_host']);
			if(!empty($cfg['ftp_port'])) $FTP_PORT = (int)$cfg['ftp_port'];
			$FTP_USER = $cfg['ftp_username'] ?? '';
			$FTP_PASS = $cfg['ftp_password'] ?? '';
			if(!empty($cfg['ftp_base_dir'])) $FTP_BASE_DIR = trim($cfg['ftp_base_dir'],'/');
			$FTP_PASSIVE = (int)$cfg['passive'];
			$FTP_SSL = (int)$cfg['ssl_enabled'];
			$FTP_ACTIVE = (int)$cfg['active'];
		}
	}
} catch(Throwable $e){ /* silencieux */ }
$requestedSource = isset($_GET['source']) ? strtolower($_GET['source']) : 'local';
$effectiveSource='local'; $ftpError=''; $ftpDiag=[]; $messages=[]; $ftpConnected=false; // nouveau flag
// Gestion AJAX (progression FTP + nettoyage dossiers vides)
if($requestedSource==='ftp' && isset($_GET['ajax']) && $_GET['ajax']=='1'){
	header('Content-Type: application/json');
	$steps=[]; $dayFolders=[]; $error='';
	$addStep=function($name,$status,$info='') use (&$steps){ $steps[]=['name'=>$name,'status'=>$status,'info'=>$info]; };
	if(!function_exists('ftp_connect')){ $error='Extension FTP non chargée'; $addStep('Init','fail',$error); echo json_encode(['error'=>$error,'steps'=>$steps,'dayFolders'=>new stdClass()]); exit; }
	if(!$FTP_HOST){ $error='FTP non configuré'; $addStep('Configuration','fail',$error); echo json_encode(['error'=>$error,'steps'=>$steps,'dayFolders'=>new stdClass()]); exit; }
	if(!$FTP_ACTIVE){ $error='FTP désactivé'; $addStep('Configuration','fail',$error); echo json_encode(['error'=>$error,'steps'=>$steps,'dayFolders'=>new stdClass()]); exit; }
	// DNS
	$t0=microtime(true);
	if(filter_var($FTP_HOST,FILTER_VALIDATE_IP)) { $hostIp=$FTP_HOST; $addStep('DNS','ok','IP directe'); }
	else { $hostIp=@gethostbyname($FTP_HOST); if($hostIp===$FTP_HOST){ $addStep('DNS','warn','Résolution échouée'); } else { $addStep('DNS','ok',"$FTP_HOST -> $hostIp"); } }
	// TCP
	$errno=0;$errstr=''; $sock=@fsockopen($hostIp,$FTP_PORT,$errno,$errstr,5);
	if(!$sock){ $addStep('TCP','warn',"$errno:$errstr"); }
	else { $lat=round((microtime(true)-$t0)*1000,1); $addStep('TCP','ok',"{$lat}ms"); @fclose($sock);}    
	// Connexion
	$conn=false; if($FTP_SSL && function_exists('ftp_ssl_connect')){ $conn=@ftp_ssl_connect($FTP_HOST,$FTP_PORT,8); if($conn) $addStep('Connect','ok','FTPS'); }
	if(!$conn){ $conn=@ftp_connect($FTP_HOST,$FTP_PORT,8); if($conn) $addStep('Connect','ok','FTP'); }
	if(!$conn){ $error='Connexion échouée'; $addStep('Connect','fail',$error); echo json_encode(['error'=>$error,'steps'=>$steps,'dayFolders'=>new stdClass()]); exit; }
	// Login
	if(!@ftp_login($conn,$FTP_USER,$FTP_PASS)){ $error='Authentification échouée'; $addStep('Login','fail',$error); @ftp_close($conn); echo json_encode(['error'=>$error,'steps'=>$steps,'dayFolders'=>new stdClass()]); exit; }
	$addStep('Login','ok','');
	// Mode
	if($FTP_PASSIVE){ @ftp_pasv($conn,true); $addStep('Mode','ok','Passif'); } else { $addStep('Mode','ok','Actif'); }
	// Base
	$remoteBase=$FTP_BASE_DIR; $baseExists=false;
	if($remoteBase===''){ $baseExists=true; $addStep('Base','ok','(racine)'); }
	elseif(@ftp_chdir($conn,$remoteBase)){ $baseExists=true; $addStep('Base','ok',$remoteBase); }
	else { $error='Dossier base introuvable'; $addStep('Base','fail',$remoteBase); }
	$remoteDays=[];
	if(!$error && $baseExists){
		$raw=@ftp_nlist($conn,'.'); if($raw===false) $raw=[]; $addStep('Lister','ok',count($raw).' entrées');
		// Nettoyage dossiers date vides (YYYY-MM-DD)
		foreach($raw as $entry){
			$entryName=basename($entry);
			if(!preg_match('/^\d{4}-\d{2}-\d{2}$/',$entryName)) continue;
			$content=@ftp_nlist($conn,$entryName);
			// Filtrer . et ..
			$realContent=[]; if(is_array($content)){ foreach($content as $c){ $bn=basename($c); if($bn!=='.' && $bn!=='..') $realContent[]=$c; } }
			if(!$content || count($realContent)===0){
				if(@ftp_rmdir($conn,$entryName)) $addStep('Nettoyage','ok','Dossier vide: '.$entryName);
			} else {
				$remoteDays[]=$entryName;
			}
		}
		// ZIP à la racine
		foreach($raw as $entry){
			$entryName=basename($entry);
			if(substr($entryName,-4)!=='.zip') continue;
			if(preg_match('/_backup_(\d{4}-\d{2}-\d{2})_/',$entryName,$m)){
				$day=$m[1];
				$dayFolders[$day]=$dayFolders[$day]??[];
				$remoteFull=($remoteBase? $remoteBase.'/' : '').$entryName;
				$rel=$remoteBase? $entryName : $remoteFull;
				$mtime=null; if(preg_match('/_backup_(\d{4}-\d{2}-\d{2})_(\d{2}-\d{2}-\d{2})/',$entryName,$mt)){ $mtime=strtotime($mt[1].' '.str_replace('-',':',$mt[2])); }
				if($mtime===null){ $mt1=@ftp_mdtm($conn,$rel); if($mt1>0) $mtime=$mt1; }
				if($mtime===null){ $mt2=@ftp_mdtm($conn,$remoteFull); if($mt2>0) $mtime=$mt2; }
				$size=@ftp_size($conn,$rel); if($size<0) $size=@ftp_size($conn,$remoteFull); if($size<0) $size=0;
				$ref=preg_replace('/_backup_.+$/','',$entryName);
				$dayFolders[$day][]= ['path'=>$remoteFull,'file'=>$entryName,'size'=>$size,'mtime'=>$mtime,'ref'=>$ref];
			}
		}
		// Dossiers date restants
		foreach($remoteDays as $dayName){
			$files=@ftp_nlist($conn,$dayName); if(!$files) continue;
			foreach($files as $f){
				if(substr($f,-4)!=='.zip') continue; $fileName=basename($f);
				$ref=preg_replace('/_backup_.+$/','',$fileName);
				$remoteFull=($remoteBase? $remoteBase.'/' : '').$dayName.'/'.$fileName;
				$relPath=$dayName.'/'.$fileName;
				$mtime=@ftp_mdtm($conn,$relPath); if($mtime<=0) $mtime=@ftp_mdtm($conn,$remoteFull);
				if($mtime<=0){ if(preg_match('/_backup_(\d{4}-\d{2}-\d{2})_(\d{2}-\d{2}-\d{2})/',$fileName,$m2)){ $mtime=strtotime($m2[1].' '.str_replace('-',':',$m2[2])); } else { $mtime=null; } }
				$size=@ftp_size($conn,$relPath); if($size<0) $size=@ftp_size($conn,$remoteFull); if($size<0) $size=0;
				$dayFolders[$dayName]=$dayFolders[$dayName]??[];
				$dayFolders[$dayName][]= ['path'=>$remoteFull,'file'=>$fileName,'size'=>$size,'mtime'=>$mtime,'ref'=>$ref];
			}
		}
	}
	@ftp_close($conn);
	if($dayFolders){ krsort($dayFolders); foreach($dayFolders as $dKey=>&$fl){ usort($fl,function($a,$b){ return ($b['mtime']??0)<=>($a['mtime']??0); }); } unset($fl); }
	echo json_encode(['error'=>$error,'steps'=>$steps,'dayFolders'=>$dayFolders]);
	exit;
}
if(!isset($gc_pdo) || !($gc_pdo instanceof PDO)){
	die('Connexion base (gc_pdo) indisponible.');
}
// Détection dynamique de la colonne compagnie_name (optionnelle)
$hasNameCol=false; try { $cols=$gc_pdo->query("SHOW COLUMNS FROM compagnies_table LIKE 'compagnie_name'"); if($cols && $cols->rowCount()>0){ $hasNameCol=true; } } catch(Exception $e) { /* ignore */ }
try{ $companies=$gc_pdo->query("SELECT compagnie_ref, ".($hasNameCol?"compagnie_name, ":"")."compagnie_db, compagnie_db_user, compagnie_db_pass FROM compagnies_table WHERE active=1 ORDER BY compagnie_ref")->fetchAll(); }catch(Exception $e){ $companies=[]; $messages[]=['type'=>'danger','text'=>'Lecture compagnies impossible: '.$e->getMessage()]; }
$companyIndex=[]; foreach($companies as $c){ $companyIndex[$c['compagnie_ref']]=$c; }
function restoreGlobalBackup($gestionPdo, $companyRow, $zipFullPath) {
    $ref = $companyRow['compagnie_ref'];
    if (!file_exists($zipFullPath)) throw new Exception('Fichier introuvable');
    $tmpBase = sys_get_temp_dir() . '/restore_global_' . uniqid();
    if (!mkdir($tmpBase, 0755, true)) throw new Exception('Impossible de créer tmp');
    $zip = new ZipArchive();
    if ($zip->open($zipFullPath) !== true) throw new Exception('Ouverture ZIP impossible');
    if (!$zip->extractTo($tmpBase)) {
        $zip->close();
        throw new Exception('Extraction échouée');
    }
    $zip->close();
    $dsn = "mysql:host=localhost;dbname={$companyRow['compagnie_db']};charset=utf8";
    try {
        $pdo = new PDO(
            $dsn,
            $companyRow['compagnie_db_user'],
            $companyRow['compagnie_db_pass'],
            [
                PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
                PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
                PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8'
            ]
        );
    } catch (Exception $e) {
        throw new Exception('Connexion DB compagnie impossible: ' . $e->getMessage());
    }
    $sqlFile = $tmpBase . '/database.sql';
    if (!file_exists($sqlFile)) throw new Exception('database.sql manquant');
    $sql = file_get_contents($sqlFile);
    $queries = array_filter(array_map('trim', explode(';', $sql)));
	// Filtrer les requêtes qui touchent users_table ou menu_items uniquement
	$filteredQueries = [];
	foreach ($queries as $q) {
		$qNorm = preg_replace('/\s+/', ' ', strtolower($q));
		// Ignore toute requête qui modifie users_table ou menu_items
		if (preg_match('/\b(insert|update|delete|drop|truncate|replace|create)\b[^;]*\b(users_table|menu_items)\b/', $qNorm)) {
			continue;
		}
		$filteredQueries[] = $q;
	}
	$exec = 0;
	$total = count($filteredQueries);
	$errors = 0;
	foreach ($filteredQueries as $q) {
		if ($q === '') continue;
		try {
			$pdo->exec($q);
			$exec++;
		} catch (Exception $qe) {
			$errors++;
		}
	}
    $filesDir = $tmpBase . '/files';
    $copied = 0;
    if (is_dir($filesDir)) {
        $target = dirname(__DIR__) . '/data/' . $ref;
        if (!is_dir($target)) mkdir($target, 0755, true);
        $it = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($filesDir, FilesystemIterator::SKIP_DOTS));
        foreach ($it as $f) {
            if ($f->isFile()) {
                $dest = $target . '/' . substr($f->getPathname(), strlen($filesDir) + 1);
                $destDir = dirname($dest);
                if (!is_dir($destDir)) mkdir($destDir, 0755, true);
                @copy($f->getPathname(), $dest);
                $copied++;
            }
        }
    }
    $del = function ($d) use (&$del) {
        if (!is_dir($d)) return;
        foreach (array_diff(scandir($d), ['.', '..']) as $c) {
            $p = $d . '/' . $c;
            if (is_dir($p)) $del($p);
            else @unlink($p);
        }
        @rmdir($d);
    };
    $del($tmpBase);
    $globalLog = __DIR__ . '/backup_general.log';
    $line = '[' . date('Y-m-d H:i:s') . "] RESTORE {$ref} depuis " . basename(dirname($zipFullPath)) . '/' . basename($zipFullPath) . " : SQL $exec/$total, erreurs=$errors, fichiers=$copied" . PHP_EOL;
    @file_put_contents($globalLog, $line, FILE_APPEND | LOCK_EX);
    if ($errors > 0) return "Restauration terminée avec $errors erreurs SQL";
    return "Restauration réussie ($exec requêtes, fichiers copiés: $copied)";
}
if($_SERVER['REQUEST_METHOD']==='POST' && ($_POST['action']??'')==='restore_global'){
	$ref=$_POST['compagnie_ref']??''; $fileRel=$_POST['backup_file']??'';
	if(!$ref||!$fileRel){
		$messages[]=['type'=>'danger','text'=>'Paramètres restauration manquants'];
	} elseif(!isset($companyIndex[$ref])){
		$messages[]=['type'=>'danger','text'=>'Compagnie inconnue'];
	} else {
		// Restauration depuis FTP (valeur préfixée)
		if(strpos($fileRel,'FTP:')===0){
			$remotePath=substr($fileRel,4);
			// Sécurisation basique du chemin
			if(strpos($remotePath,'..')!==false || substr($remotePath,-4)!='.zip'){
				$messages[]=['type'=>'danger','text'=>'Chemin FTP invalide'];
			} elseif($FTP_BASE_DIR!=='' && strpos($remotePath,$FTP_BASE_DIR.'/')!==0){
				$messages[]=['type'=>'danger','text'=>'En dehors du dossier base FTP'];
			} elseif(!preg_match('/'.preg_quote($ref,'/').'_backup_/',$remotePath)){
				$messages[]=['type'=>'danger','text'=>'Le fichier ne correspond pas à la compagnie'];
			} elseif(!$FTP_ACTIVE){
				$messages[]=['type'=>'danger','text'=>'FTP désactivé'];
			} elseif(!function_exists('ftp_connect')){
				$messages[]=['type'=>'danger','text'=>'Extension FTP absente'];
			} else {
				// Connexion & téléchargement temporaire
				$conn=false; if($FTP_SSL && function_exists('ftp_ssl_connect')){ $conn=@ftp_ssl_connect($FTP_HOST,$FTP_PORT,15); }
				if(!$conn){ $conn=@ftp_connect($FTP_HOST,$FTP_PORT,15); }
				if(!$conn){ $messages[]=['type'=>'danger','text'=>'Connexion FTP impossible']; }
				elseif(!@ftp_login($conn,$FTP_USER,$FTP_PASS)){
					$messages[]=['type'=>'danger','text'=>'Authentification FTP échouée'];
					@ftp_close($conn);
				} else {
					if($FTP_PASSIVE) @ftp_pasv($conn,true);
					$tmpDir=sys_get_temp_dir().'/restore_ftp'; if(!is_dir($tmpDir)) @mkdir($tmpDir,0755,true);
					$tmpFile=$tmpDir.'/'.basename($remotePath);
					$ok=@ftp_get($conn,$tmpFile,$remotePath,FTP_BINARY);
					@ftp_close($conn);
					if(!$ok || !file_exists($tmpFile)){
						$messages[]=['type'=>'danger','text'=>'Téléchargement FTP échoué'];
					} else {
						try{
							$msg=restoreGlobalBackup($gc_pdo,$companyIndex[$ref],$tmpFile);
							$messages[]=['type'=>'success','text'=>$msg.' (depuis FTP)'];
						}catch(Exception $e){
							$messages[]=['type'=>'danger','text'=>'Erreur restauration (FTP): '.$e->getMessage()];
						}
						@unlink($tmpFile);
					}
				}
			}
		} else {
			// Restauration locale
			$baseDir=realpath(dirname(__DIR__).'/backup_general');
			$fullPath=realpath(dirname(__DIR__).'/'.$fileRel);
			if(!$fullPath||strpos($fullPath,$baseDir)!==0||!preg_match('/'.preg_quote($ref,'/').'_backup_/',$fullPath)){
				$messages[]=['type'=>'danger','text'=>'Fichier invalide'];
			} else {
				try{ $msg=restoreGlobalBackup($gc_pdo,$companyIndex[$ref],$fullPath); $messages[]=['type'=>'success','text'=>$msg]; }
				catch(Exception $e){ $messages[]=['type'=>'danger','text'=>'Erreur restauration: '.$e->getMessage()]; }
			}
		}
	}
}
if($_SERVER['REQUEST_METHOD']==='POST' && ($_POST['action']??'')==='run_cron'){ ob_start(); try{ define('BACKUP_EMBED',true); include dirname(__DIR__).'/backup_cron_general.php'; }catch(Throwable $e){ echo '[ERREUR] '.$e->getMessage(); } $out=trim(ob_get_clean()); header('Content-Type: application/json'); echo json_encode(['ok'=>true,'output'=>$out]); exit; }
// Suppression d'un fichier FTP
if($_SERVER['REQUEST_METHOD']==='POST' && ($_POST['action']??'')==='delete_ftp'){
	$remotePath = $_POST['remote_path'] ?? '';
	if(!$FTP_ACTIVE){
		$messages[]=['type'=>'danger','text'=>'FTP désactivé'];
	} elseif(!$remotePath){
		$messages[]=['type'=>'danger','text'=>'Chemin FTP manquant'];
	} elseif($FTP_BASE_DIR!=='' && strpos($remotePath, $FTP_BASE_DIR.'/')!==0){
		$messages[]=['type'=>'danger','text'=>'Chemin FTP invalide (base)'];
	} else {
		if(!function_exists('ftp_connect')){
			$messages[]=['type'=>'danger','text'=>'Extension FTP absente'];
		} else {
			$conn=false; if($FTP_SSL && function_exists('ftp_ssl_connect')){ $conn=@ftp_ssl_connect($FTP_HOST,$FTP_PORT,8); }
			if(!$conn){ $conn=@ftp_connect($FTP_HOST,$FTP_PORT,8); }
			if(!$conn){ $messages[]=['type'=>'danger','text'=>'Connexion FTP impossible']; }
			elseif(!@ftp_login($conn,$FTP_USER,$FTP_PASS)){ $messages[]=['type'=>'danger','text'=>'Auth FTP échouée']; @ftp_close($conn); }
			else {
				if($FTP_PASSIVE) @ftp_pasv($conn,true);
				if(@ftp_delete($conn,$remotePath)){
					$messages[]=['type'=>'success','text'=>'Fichier supprimé: '.htmlspecialchars(basename($remotePath))];
				} else {
					$messages[]=['type'=>'danger','text'=>'Suppression échouée pour: '.htmlspecialchars(basename($remotePath))];
				}
				@ftp_close($conn);
			}
		}
	}
}
// Suppression d'un fichier local
if($_SERVER['REQUEST_METHOD']==='POST' && ($_POST['action']??'')==='delete_local'){
	$localPathRel = $_POST['local_path'] ?? '';
	if(!$localPathRel){
		$messages[]=['type'=>'danger','text'=>'Chemin local manquant'];
	} else {
		$baseDirReal = realpath(dirname(__DIR__).'/backup_general');
		$targetReal = realpath(dirname(__DIR__).'/'.$localPathRel);
		if(!$targetReal || !$baseDirReal || strpos($targetReal,$baseDirReal)!==0){
			$messages[]=['type'=>'danger','text'=>'Chemin local invalide'];
		} elseif(substr($targetReal,-4) !== '.zip'){
			$messages[]=['type'=>'danger','text'=>'Fichier non autorisé'];
		} else {
			if(@unlink($targetReal)){
				$messages[]=['type'=>'success','text'=>'Fichier local supprimé'];
				// Tentative suppression dossier date s'il devient vide
				$dayDir = dirname($targetReal);
				if($dayDir && $dayDir !== $baseDirReal){
					$remaining = array_diff(@scandir($dayDir)?:[],['.','..']);
					if(count($remaining)===0) @rmdir($dayDir);
				}
			} else {
				$messages[]=['type'=>'danger','text'=>'Suppression locale échouée'];
			}
		}
	}
}
// Téléchargement direct d'un fichier FTP (stream via serveur)
if(isset($_GET['action']) && $_GET['action']==='download_ftp'){
	$remotePath=$_GET['remote_path'] ?? '';
	if($remotePath && $FTP_ACTIVE && $FTP_HOST && $FTP_USER){
		$conn = ($FTP_SSL? @ftp_ssl_connect($FTP_HOST,$FTP_PORT,10): @ftp_connect($FTP_HOST,$FTP_PORT,10));
		if($conn && @ftp_login($conn,$FTP_USER,$FTP_PASS)){
			if($FTP_PASSIVE) @ftp_pasv($conn,true);
			// Validation chemin
			if($FTP_BASE_DIR && strpos($remotePath,$FTP_BASE_DIR)===0 && substr($remotePath,-4)==='.zip'){
				$tmp=tempnam(sys_get_temp_dir(),'ftpdl_');
				$ok=@ftp_get($conn,$tmp,$remotePath,FTP_BINARY);
				if($ok && is_file($tmp)){
					$filename=basename($remotePath);
					header('Content-Type: application/zip');
					header('Content-Length: '.filesize($tmp));
					header('Content-Disposition: attachment; filename="'.rawurlencode($filename).'"');
					readfile($tmp);
					@unlink($tmp);
					exit;
				}
				@unlink($tmp);
			}
		}
	}
	header('HTTP/1.1 404 Not Found');
	echo 'Fichier introuvable';
	exit;
}
$globalBase=dirname(__DIR__).'/backup_general'; $dayFolders=[]; if($requestedSource==='ftp'){
	// Passage en mode FTP avec chargement asynchrone côté client
	$effectiveSource='ftp';
	$ftpConnected=true; // empêche fallback local
	$ftpDiag=[''];
}
if(!$dayFolders){
	// Ne pas basculer vers local si utilisateur a demandé FTP et connexion réussie (même vide, sans erreur)
	$canFallback = !($requestedSource==='ftp' && $ftpConnected && !$ftpError);
	if($canFallback && is_dir($globalBase)){
		// Nettoyage local: supprimer dossiers date vides (YYYY-MM-DD) demandée
		if($effectiveSource==='local'){
			foreach(scandir($globalBase) as $dClean){
				if($dClean==='.'||$dClean==='..') continue; $pClean=$globalBase.'/'.$dClean;
				if(is_dir($pClean) && preg_match('/^\d{4}-\d{2}-\d{2}$/',$dClean)){
					$contents=array_diff(scandir($pClean),['.','..']);
					if(count($contents)===0){ @rmdir($pClean); }
				}
			}
		}
		foreach(scandir($globalBase) as $d){ if($d==='.'||$d==='..') continue; $p=$globalBase.'/'.$d; if(is_dir($p) && preg_match('/^\d{4}-\d{2}-\d{2}$/',$d)){ $dayFolders[$d]=[]; foreach(glob($p.'/*.zip') as $zip){ $dayFolders[$d][]= ['path'=>$zip,'file'=>basename($zip),'size'=>filesize($zip),'mtime'=>filemtime($zip),'ref'=>preg_replace('/_backup_.+$/','',basename($zip))]; } } }
		if($dayFolders){ krsort($dayFolders); }
	}
}
foreach($dayFolders as $dKey=>&$fList){ usort($fList,function($a,$b){ return ($b['mtime']??0)<=>($a['mtime']??0); }); } unset($fList);
$logFile=__DIR__.'/backup_general.log'; $logLines=[]; if(file_exists($logFile)){ $logLines=array_reverse(array_slice(file($logFile,FILE_IGNORE_NEW_LINES),-300)); }
// Préparer diagnostics locaux si source locale
$localSteps=[]; if($effectiveSource==='local'){
	$basePath=$globalBase; $exists=is_dir($basePath);
    if($exists){
		$writable=is_writable($basePath);
		$localSteps[]=['name'=>'Permissions','status'=>$writable?'ok':'warn','info'=>$writable?'Écriture OK':'Lecture seule'];
		$dayCount=0; $zipCount=0; $totalSize=0; foreach($dayFolders as $d=>$files){ $dayCount++; foreach($files as $f){ $zipCount++; $totalSize+=$f['size']??0; } }
		$localSteps[]=['name'=>'Dossiers','status'=>'ok','info'=>$dayCount.' jours'];
		$localSteps[]=['name'=>'Fichiers','status'=>'ok','info'=>$zipCount.' zip'];
		$free=@disk_free_space($basePath); if($free!==false){ $localSteps[]=['name'=>'Espace libre','status'=>'ok','info'=>round($free/1024/1024).' MB']; }
		if($totalSize>0){ $localSteps[]=['name'=>'Taille totale','status'=>'ok','info'=>round($totalSize/1024/1024,1).' MB']; }
	}
}
?><!DOCTYPE html><html lang="fr"><head><meta charset="utf-8"><title>Backups Multi-Compagnies</title><link rel="stylesheet" href="../vendor/bootstrap/css/bootstrap.min.css"><link rel="stylesheet" href="../vendor/fontawesome-free/css/all.min.css"><link rel="stylesheet" href="../css/ruang-admin.min.css"><style>pre.log{max-height:400px;overflow:auto;background:#111;color:#d1d1d1;padding:1rem;font-size:12px;border-radius:4px}.small-badge{font-size:11px}.source-banner{font-size:1rem;padding:.65rem .9rem;border-radius:.5rem}.source-ftp{background:#0d6efd;color:#fff}.source-local{background:#198754;color:#fff}.source-banner .h5{margin:0 .35rem 0 .35rem;display:inline-block;font-size:1.15rem;font-weight:700;letter-spacing:.5px;text-shadow:0 1px 1px rgba(0,0,0,.25)}.source-banner .badge-danger{font-size:.7rem;vertical-align:middle}</style></head><body id="page-top"><div id="wrapper"><?php include __DIR__.'/sidebar.php'; ?><div id="content-wrapper" class="d-flex flex-column"><div id="content"><?php include __DIR__.'/topbar.php'; ?><div class="container-fluid">
<div class="d-flex flex-wrap justify-content-between align-items-center mb-3 gap-2"><div class="d-flex align-items-center mb-2 mb-sm-0"><h3 class="mb-0 mr-3"><i class="fas fa-database text-primary"></i> Backups</h3><button id="run-cron" class="btn btn-sm btn-success mr-2"><i class="fas fa-play"></i> Lancer Backup</button></div><div class="btn-group mb-2 mb-sm-0"><a href="?source=ftp" class="btn btn-sm <?php echo $effectiveSource==='ftp'?'btn-info':'btn-outline-info'; ?>"><i class="fas fa-server"></i> FTP</a><a href="?source=local" class="btn btn-sm <?php echo $effectiveSource==='local'?'btn-info':'btn-outline-info'; ?>"><i class="fas fa-hdd"></i> Local</a></div></div>
<?php $srcClass=$effectiveSource==='ftp'?'source-ftp':'source-local'; $srcLabel=strtoupper($effectiveSource); ?>
<div class="alert source-banner d-flex flex-column flex-md-row justify-content-between align-items-md-center <?php echo $srcClass; ?> mb-3">
	<div class="mb-2 mb-md-0">
		<i class="fas fa-plug"></i>
		<span>Source:</span>
		<span class="h5 mb-0"><?php echo $srcLabel; ?></span>
		<?php if($requestedSource==='ftp' && $effectiveSource!=='ftp'): ?>
			<span class="badge badge-danger ml-2">FTP indisponible</span>
		<?php endif; ?>
	</div>
	<div class="small text-light" style="opacity:.9; max-width:100%; word-break:break-word;">
		<?php if(!empty($ftpDiag)) echo htmlspecialchars(implode(' | ',$ftpDiag)); ?>
		<?php if($requestedSource==='ftp' && $effectiveSource!=='ftp' && $ftpError) echo ' - '.htmlspecialchars($ftpError); ?>
	</div>
</div>
<?php foreach($messages as $m): ?><div class="alert alert-<?php echo htmlspecialchars($m['type']); ?> py-2"><?php echo $m['text']; ?></div><?php endforeach; ?>
<div class="card mb-4"><div class="card-header py-2"><strong>Restauration</strong></div><div class="card-body"><form method="POST" id="restoreForm" class="form-inline"><input type="hidden" name="action" value="restore_global"><label class="mr-2">Compagnie:</label><select name="compagnie_ref" id="compagnie_ref" class="form-control mr-2" required><option value="">-- Choisir --</option><?php foreach($companies as $c): $label=$c['compagnie_ref']; if($hasNameCol && !empty($c['compagnie_name'])){ $label.=' - '.$c['compagnie_name']; } ?><option value="<?php echo htmlspecialchars($c['compagnie_ref']); ?>"><?php echo htmlspecialchars($label); ?></option><?php endforeach; ?></select><label class="mr-2">Backup:</label><select name="backup_file" id="backup_file" class="form-control mr-2" required disabled><option value="">Sélectionner d'abord la compagnie</option></select><button type="button" class="btn btn-warning" disabled id="btnRestore"><i class="fas fa-undo"></i> Restaurer</button></form></div></div>
<div class="card mb-4"><div class="card-header py-2 d-flex justify-content-between align-items-center"><strong><i class="fas fa-plug"></i> Connexion</strong><?php if($effectiveSource==='ftp'): ?><span class="small text-light" id="ftp-progress-status"></span><?php endif; ?></div><div class="card-body p-2">
<?php if($effectiveSource==='ftp'): ?>
	<div id="ftp-progress" class="mb-3">
		<div class="progress" style="height:20px;">
			<div class="progress-bar progress-bar-striped progress-bar-animated" id="ftp-progress-bar" role="progressbar" style="width:5%">Initialisation...</div>
		</div>
		<ul class="list-unstyled small mt-2 mb-0" id="ftp-steps"></ul>
	</div>
	<h6 class="mb-2">  <i class="fas fa-database"></i> <strong>Backups</strong></h6>
	<div id="backups-content"><p class="text-muted mb-0">Chargement...</p></div>
<?php else: ?>
	<ul class="list-unstyled small mb-3" id="local-steps">
		<?php foreach($localSteps as $s): if($s['name']==='Base') continue; ?>
		<li><strong><?php echo htmlspecialchars($s['name']); ?></strong>: <?php echo htmlspecialchars($s['info']); ?> <?php if($s['status']==='ok'): ?><span class="text-success">[ok]</span><?php elseif($s['status']==='warn'): ?><span class="text-warning">[avert]</span><?php else: ?><span class="text-danger">[fail]</span><?php endif; ?></li>
		<?php endforeach; ?>
	</ul>
	<h6 class="mb-2"><i class="fas fa-database"></i> Backups</h6>
	<?php
	if(!$dayFolders): ?>
		<p class="text-muted mb-0">Aucun backup trouvé.</p>
	<?php else:
		$__localBackupList=[];
		foreach($dayFolders as $day=>$files): ?>
			<h6 class="mt-3 mb-2"><i class="far fa-calendar-alt"></i> <?php echo $day; ?></h6>
			<div class="table-responsive"><table class="table table-sm table-bordered mb-2"><thead class="thead-light"><tr><th>Compagnie</th><th>Fichier</th><th>Taille</th><th>Heure</th><th>Actions</th></tr></thead><tbody>
			<?php foreach($files as $f): $rel='backup_general/'.$day.'/'.$f['file']; $__localBackupList[]=[
				'value'=>$rel,
				'file'=>$f['file'],
				'path'=>$rel,
				'day'=>$day,
				'ref'=>$f['ref'],
				'sizeKB'=>number_format(($f['size']??0)/1024,1)
			]; ?>
			<tr>
				<td><span class="badge badge-primary small-badge"><?php echo htmlspecialchars($f['ref']); ?></span></td>
				<td><?php echo htmlspecialchars($f['file']); ?></td>
				<td><?php echo number_format(($f['size']??0)/1024,1); ?> KB</td>
				<td><?php echo ($f['mtime']?date('H:i:s',$f['mtime']):'-'); ?></td>
				<td class="text-nowrap"><a class="btn btn-sm btn-outline-secondary" href="<?php echo '../backup_general/'.rawurlencode($day).'/'.rawurlencode($f['file']); ?>" download title="Télécharger"><i class="fas fa-download"></i></a> <button type="button" class="btn btn-sm btn-outline-danger btn-del-local" data-path="<?php echo htmlspecialchars($rel); ?>" data-file="<?php echo htmlspecialchars($f['file']); ?>" title="Supprimer"><i class="fas fa-trash"></i></button></td>
			</tr>
			<?php endforeach; ?>
			</tbody></table></div>
		<?php endforeach; ?>
		<script type="application/json" id="localBackupData"><?php echo json_encode($__localBackupList,JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES); ?></script>
	<?php endif; ?>
<?php endif; ?>
</div></div>

<!-- Modal Suppression FTP -->
<div class="modal fade" id="modalDeleteFtp" tabindex="-1" role="dialog" aria-hidden="true">
	<div class="modal-dialog modal-dialog-centered" role="document">
		<div class="modal-content">
			<div class="modal-header py-2"><h6 class="modal-title"><i class="fas fa-exclamation-triangle text-danger"></i> Confirmer suppression</h6><button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button></div>
			<form method="POST" class="mb-0">
				<div class="modal-body">
					<input type="hidden" name="action" id="del_action" value="delete_ftp">
					<input type="hidden" name="remote_path" id="del_ftp_remote_path" value="">
					<input type="hidden" name="local_path" id="del_local_path" value="">
					<p class="mb-1 small text-muted">Fichier:</p>
					<p id="del_ftp_file" class="font-weight-bold mb-0"></p>
					<div class="alert alert-warning mt-3 mb-0 py-2"><i class="fas fa-info-circle"></i> Cette action est irréversible.</div>
				</div>
				<div class="modal-footer py-2">
					<button type="button" class="btn btn-sm btn-secondary" data-dismiss="modal">Annuler</button>
					<button type="submit" class="btn btn-sm btn-danger"><i class="fas fa-trash"></i> Supprimer</button>
				</div>
			</form>
		</div>
	</div>
<div class="card mb-4"><div class="card-header py-2 d-flex justify-content-between align-items-center"><strong>Journal (backup_general.log)</strong><button class="btn btn-sm btn-outline-primary" onclick="location.reload()"><i class="fas fa-sync"></i></button></div><div class="card-body p-2"><?php if($logLines): ?><pre class="log mb-0"><?php echo htmlspecialchars(implode("\n",$logLines)); ?></pre><?php else: ?><p class="text-muted mb-0">Aucun log.</p><?php endif; ?></div></div>
<p class="text-center text-muted small mb-4">&copy; <?php echo date('Y'); ?> Backups</p>
</div></div><footer class="sticky-footer bg-white"><div class="container my-auto"><div class="text-center my-auto small">&copy; <?php echo date('Y'); ?> Backups</div></div></footer></div></div>
<!-- Modals -->
<div class="modal fade" id="modalRunCron" tabindex="-1" role="dialog" aria-hidden="true">
	<div class="modal-dialog modal-dialog-centered" role="document">
		<div class="modal-content">
			<div class="modal-header py-2">
				<h6 class="modal-title"><i class="fas fa-play text-success"></i> Lancer un backup</h6>
				<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
			</div>
			<div class="modal-body">
				<p class="mb-2">Confirmer le lancement immédiat du backup global ?</p>
				<div id="runCronFeedback" class="small text-muted">Prêt.</div>
			</div>
			<div class="modal-footer py-2">
				<button type="button" class="btn btn-sm btn-secondary" data-dismiss="modal">Annuler</button>
				<button type="button" class="btn btn-sm btn-success" id="btnConfirmRunCron"><i class="fas fa-check"></i> Confirmer</button>
			</div>
		</div>
	</div>
			</div>
			<!-- Modal Confirmation Restauration -->
			<div class="modal fade" id="modalRestoreConfirm" tabindex="-1" role="dialog" aria-hidden="true">
				<div class="modal-dialog modal-dialog-centered" role="document">
					<div class="modal-content">
						<div class="modal-header py-2">
							<h6 class="modal-title"><i class="fas fa-undo"></i> Confirmer la restauration</h6>
							<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
						</div>
						<div class="modal-body py-3">
							<p class="mb-2">Vous allez restaurer la compagnie <strong id="restoreCompagnieLbl"></strong> avec le backup:</p>
							<p class="mb-0 text-monospace small" id="restoreBackupLbl"></p>
							<div class="alert alert-warning mt-3 mb-0 py-2"><i class="fas fa-exclamation-triangle"></i> Cette opération écrasera les données actuelles.</div>
						</div>
						<div class="modal-footer py-2">
							<button type="button" class="btn btn-sm btn-secondary" data-dismiss="modal">Annuler</button>
							<button type="button" class="btn btn-sm btn-warning" id="btnConfirmRestore"><i class="fas fa-check"></i> Confirmer</button>
						</div>
					</div>
				</div>
			</div>
</div>
<script src="../vendor/jquery/jquery.min.js"></script><script src="../vendor/bootstrap/js/bootstrap.bundle.min.js"></script><script src="../vendor/jquery-easing/jquery.easing.min.js"></script><script src="../js/ruang-admin.min.js"></script><script>// Modal lancement backup
 (function(){
	 const btn=document.getElementById('run-cron');
	 if(btn){ btn.addEventListener('click',function(e){ e.preventDefault(); if(window.jQuery){ window.jQuery('#modalRunCron').modal('show'); }}); }
	 function runBackup(){
		 const b=document.getElementById('btnConfirmRunCron'); const fb=document.getElementById('runCronFeedback'); if(!b) return;
		 b.disabled=true; const old=b.innerHTML; b.innerHTML='<i class="fas fa-spinner fa-spin"></i> En cours'; if(fb) fb.textContent='Exécution en cours...';
		 fetch('backups.php',{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded'},body:'action=run_cron'})
			 .then(r=>r.json())
			 .then(js=>{ if(fb) fb.textContent='Terminé'; b.innerHTML='<i class="fas fa-check"></i> Fini'; setTimeout(()=>{ if(window.jQuery) window.jQuery('#modalRunCron').modal('hide'); location.reload(); },600); })
			 .catch(e=>{ if(fb) fb.textContent='Erreur: '+e; b.disabled=false; b.innerHTML=old; });
	 }
	 document.addEventListener('click',e=>{ if(e.target && e.target.id==='btnConfirmRunCron'){ runBackup(); }});
 })();
// Filtrage des backups selon la compagnie sélectionnée
(function(){
	const selComp=document.getElementById('compagnie_ref');
	const selBackup=document.getElementById('backup_file');
	if(!selComp||!selBackup) return;
	let allOptions=[]; // sera rempli à la première construction (local) ou injection (FTP)
	let localLoaded=false;
	function rebuildAllOptions(){
		allOptions=[...selBackup.querySelectorAll('option')].filter(o=>o.value!=='');
		if(!allOptions.length && !localLoaded){
			const dataEl=document.getElementById('localBackupData');
			if(dataEl){
				try{
					const arr=JSON.parse(dataEl.textContent||'[]');
					arr.forEach(it=>{
						const op=document.createElement('option');
						op.value=it.value;
						op.textContent=it.day+' / '+it.file+' ('+it.sizeKB+'KB)';
						op.setAttribute('data-ref',it.ref);
						allOptions.push(op);
					});
					localLoaded=true;
				}catch(e){ console.warn('Parse localBackupData',e); }
			}
		}
	}
	function filter(){
		const ref=selComp.value.trim();
		const current=selBackup.value;
		if(!ref){
			selBackup.innerHTML='<option value="">Sélectionner d\'abord la compagnie</option>';
			selBackup.disabled=true; document.getElementById('btnRestore').disabled=true; return;
		}
		if(!allOptions.length){ rebuildAllOptions(); }
		selBackup.innerHTML='<option value="">-- Choisir --</option>';
		allOptions.forEach(op=>{ if(op.getAttribute('data-ref')===ref){ selBackup.appendChild(op.cloneNode(true)); } });
		selBackup.disabled=false; document.getElementById('btnRestore').disabled=false;
		// Rétablir selection si encore visible
		if(current){ const opt=[...selBackup.options].find(o=>o.value===current); if(opt) selBackup.value=current; }
	}
	selComp.addEventListener('change',filter);
	// Expose pour usage après chargement FTP
	window.__backupFilter={rebuildAllOptions,filter};
})();
// Modal confirmation restauration
(function(){
	const btnRestore=document.getElementById('btnRestore');
	const form=document.getElementById('restoreForm');
	const selComp=document.getElementById('compagnie_ref');
	const selBackup=document.getElementById('backup_file');
	if(!btnRestore||!form) return;
	btnRestore.addEventListener('click',function(){
		if(btnRestore.disabled) return;
		const comp=selComp.options[selComp.selectedIndex]?.textContent||'';
		const backup=selBackup.options[selBackup.selectedIndex]?.textContent||'';
		document.getElementById('restoreCompagnieLbl').textContent=comp;
		document.getElementById('restoreBackupLbl').textContent=backup;
		if(window.jQuery){ window.jQuery('#modalRestoreConfirm').modal('show'); }
	});
	document.addEventListener('click',function(e){
		if(e.target && e.target.id==='btnConfirmRestore'){
			form.submit();
		}
	});
})();
// Gestion modal suppression FTP
(function(){
	const modalId='modalDeleteFtp';
	const modal=document.getElementById(modalId);
	if(!modal) return;
	document.addEventListener('click',function(e){
		let btn=e.target.closest('.btn-del-ftp');
		let mode='ftp';
		if(!btn){ btn=e.target.closest('.btn-del-local'); mode='local'; }
		if(!btn) return;
		document.getElementById('del_ftp_remote_path').value='';
		document.getElementById('del_local_path').value='';
		if(mode==='ftp'){
			document.getElementById('del_action').value='delete_ftp';
			document.getElementById('del_ftp_remote_path').value=btn.getAttribute('data-path');
		} else {
			document.getElementById('del_action').value='delete_local';
			document.getElementById('del_local_path').value=btn.getAttribute('data-path');
		}
		document.getElementById('del_ftp_file').textContent=btn.getAttribute('data-file');
		if(window.jQuery){ window.jQuery('#'+modalId).modal('show'); }
	});
	// Téléchargement FTP
	document.addEventListener('click',function(e){
		const dl=e.target.closest('.btn-dl-ftp');
		if(!dl) return;
		const p=dl.getAttribute('data-path');
		if(p){ window.open('backups.php?action=download_ftp&remote_path='+encodeURIComponent(p),'_blank'); }
	});
})();
// Chargement dynamique FTP avec progression
(function(){
	if('<?php echo $effectiveSource; ?>'!=='ftp') return;
	const bar=document.getElementById('ftp-progress-bar');
	const stepsUl=document.getElementById('ftp-steps');
	const content=document.getElementById('backups-content');
	function setBar(pct,text,cls){ bar.style.width=pct+'%'; if(text) bar.textContent=text; if(cls) bar.className='progress-bar '+cls; }
	let done=false; let simPct=5;
	const simTimer=setInterval(()=>{ if(done) return; simPct=Math.min(simPct + (simPct<50?5:2),80); setBar(simPct); },700);
	const controller=new AbortController();
	const timeout=setTimeout(()=>{ if(done) return; controller.abort(); done=true; clearInterval(simTimer); setBar(100,'Timeout','bg-danger'); stepsUl.innerHTML='<li class="text-danger">Délai dépassé (connexion lente ou bloquée)</li>'; content.innerHTML='<p class="text-danger mb-0">Aucune réponse FTP.</p>'; },15000);
	fetch('backups.php?source=ftp&ajax=1&_='+Date.now(),{signal:controller.signal,cache:'no-store'})
		.then(r=>r.json())
		.then(js=>{
			done=true; clearInterval(simTimer); clearTimeout(timeout);
			const total = (js.steps||[]).length || 1;
			stepsUl.innerHTML='';
			(js.steps||[]).forEach((s,i)=>{
				const li=document.createElement('li');
				li.innerHTML='<span class="mr-1">'+(i+1)+'</span> <strong>'+s.name+'</strong>: '+(s.info||'')+' '+(s.status==='fail'?'<span class="text-danger">[échec]</span>':(s.status==='warn'?'<span class="text-warning">[avert]</span>':'<span class="text-success">[ok]</span>'));
				stepsUl.appendChild(li);
				const pct=Math.round(((i+1)/total)*90);
				setBar(pct,'Étape '+(i+1)+'/'+total);
			});
			if(js.error){ setBar(100,'Erreur','bg-danger'); content.innerHTML='<p class="text-danger mb-0">'+js.error+'</p>'; return; }
			const df=js.dayFolders || {};
			const days=Object.keys(df);
			if(!days.length){ setBar(100,'Terminé (0)','bg-info'); content.innerHTML='<p class="text-muted mb-0">Aucun backup trouvé.</p>'; return; }
			days.sort().reverse();
			let html='';
			let restoreOptions=[];
			days.forEach(day=>{
				html+='<h6 class="mt-3 mb-2"><i class="far fa-calendar-alt"></i> '+day+'</h6>';
				html+='<div class="table-responsive"><table class="table table-sm table-bordered mb-2"><thead class="thead-light"><tr><th>Compagnie</th><th>Fichier</th><th>Taille</th><th>Heure</th><th>Actions</th></tr></thead><tbody>';
				(df[day]||[]).forEach(f=>{
					const sizeKb=(f.size? (f.size/1024).toFixed(1):'0.0');
					const mtime=f.mtime? new Date(f.mtime*1000).toLocaleTimeString('fr-FR') : '-';
					html+='<tr>'+
						'<td><span class="badge badge-primary small-badge">'+f.ref+'</span></td>'+
						'<td>'+f.file+'</td>'+
						'<td>'+sizeKb+' KB</td>'+
						'<td>'+mtime+'</td>'+
						'<td class="text-nowrap">'+
						'<button type="button" class="btn btn-sm btn-outline-secondary btn-dl-ftp mr-1" data-path="'+f.path+'" title="Télécharger"><i class="fas fa-download"></i></button>'+
						'<button type="button" class="btn btn-sm btn-outline-danger btn-del-ftp" data-path="'+f.path+'" data-file="'+f.file+'" title="Supprimer"><i class="fas fa-trash"></i></button>'+
						'</td>'+
						'</tr>';
					// Option restauration (prefix FTP:)
					restoreOptions.push('<option value="'+('FTP:'+f.path.replace(/"/g,'&quot;'))+'" data-ref="'+f.ref+'">[FTP] '+day+' / '+f.file+' ('+sizeKb+'KB)</option>');
				});
				html+='</tbody></table></div>';
			});
			content.innerHTML=html;
			setBar(100,'Terminé','bg-success');
			setTimeout(()=>{ bar.classList.remove('progress-bar-animated','progress-bar-striped'); },1200);
			// Injecter options dans select restauration
			try{
				const sel=document.getElementById('backup_file');
				if(sel){
					// Ne dupliquer que si pas déjà injecté
					const hadFtp=[...sel.options].some(o=>o.textContent.startsWith('[FTP]'));
					if(!hadFtp && restoreOptions.length){
						restoreOptions.forEach(opHtml=>{ const tmp=document.createElement('div'); tmp.innerHTML=opHtml; const op=tmp.firstChild; sel.appendChild(op); });
						if(window.__backupFilter){ window.__backupFilter.rebuildAllOptions(); window.__backupFilter.filter(); }
					}
				}
			}catch(e){ console.warn('Inject restore options error',e); }
		})
		.catch(err=>{ if(done) return; done=true; clearInterval(simTimer); clearTimeout(timeout); setBar(100,'Erreur','bg-danger'); stepsUl.innerHTML='<li class="text-danger">'+(err.name==='AbortError'?'Requête annulée':'Erreur: '+err)+'</li>'; content.innerHTML='<p class="text-danger">'+err+'</p>'; });
})();
</script></body></html>
