Files
wevads-platform/scripts/pages-index.php
2026-02-26 04:53:11 +01:00

825 lines
46 KiB
PHP
Executable File

<?php
/**
* WEVAL - Index Complet des Pages
* Pages Natives (MVC) + Standalone
* Avec vérification status HTTP
*/
session_start();
error_reporting(E_ALL);
$serverIp = '89.167.40.150';
$port = '5821';
$baseUrl = "http://{$serverIp}:{$port}";
// =============================================
// DÉFINITION DES PAGES NATIVES (Menu Application)
// =============================================
$nativePages = [
'Dashboard' => [
'icon' => 'fa-tachometer-alt',
'color' => '#22d3ee',
'pages' => [
['url' => '/dashboard.html', 'name' => 'Dashboard Principal', 'desc' => 'Vue d\'ensemble'],
['url' => '/index.php', 'name' => 'Index', 'desc' => 'Point d\'entrée MVC'],
['url' => '/index.php?controller=Dashboard', 'name' => 'Dashboard Controller', 'desc' => 'Contrôleur dashboard'],
]
],
'Production' => [
'icon' => 'fa-paper-plane',
'color' => '#10b981',
'pages' => [
['url' => '/index.php?controller=Send', 'name' => 'Send', 'desc' => 'Envoi campagnes'],
['url' => '/index.php?controller=Tests', 'name' => 'Tests', 'desc' => 'Tests délivrabilité'],
['url' => '/index.php?controller=Processes', 'name' => 'Processes', 'desc' => 'Processus en cours'],
['url' => '/index.php?controller=Statistics', 'name' => 'Statistics', 'desc' => 'Statistiques'],
]
],
'Serveurs & MTA' => [
'icon' => 'fa-server',
'color' => '#f59e0b',
'pages' => [
['url' => '/index.php?controller=Servers', 'name' => 'Servers', 'desc' => 'Liste serveurs PMTA'],
['url' => '/index.php?controller=Servers&action=add', 'name' => 'Add Server', 'desc' => 'Ajouter serveur'],
['url' => '/index.php?controller=Domains', 'name' => 'Domains', 'desc' => 'Gestion domaines'],
['url' => '/index.php?controller=Vmtas', 'name' => 'VMTAs', 'desc' => 'Virtual MTAs'],
['url' => '/index.php?controller=IpWarmup', 'name' => 'IP Warmup', 'desc' => 'Échauffement IP'],
]
],
'Cloud Providers' => [
'icon' => 'fa-cloud',
'color' => '#ef4444',
'pages' => [
['url' => '/index.php?controller=Providers', 'name' => 'Providers', 'desc' => 'Liste providers'],
['url' => '/index.php?controller=HuaweiCloud', 'name' => 'Huawei Cloud', 'desc' => 'Gestion Huawei'],
['url' => '/index.php?controller=GoogleCloud', 'name' => 'Google Cloud', 'desc' => 'Gestion GCP'],
]
],
'Email Marketing' => [
'icon' => 'fa-envelope',
'color' => '#8b5cf6',
'pages' => [
['url' => '/index.php?controller=Lists', 'name' => 'Lists', 'desc' => 'Listes email'],
['url' => '/index.php?controller=Templates', 'name' => 'Templates', 'desc' => 'Modèles email'],
['url' => '/index.php?controller=Subjects', 'name' => 'Subjects', 'desc' => 'Objets email'],
['url' => '/index.php?controller=Headers', 'name' => 'Headers', 'desc' => 'En-têtes email'],
['url' => '/index.php?controller=Placeholders', 'name' => 'Placeholders', 'desc' => 'Variables dynamiques'],
]
],
'Affiliate' => [
'icon' => 'fa-handshake',
'color' => '#06b6d4',
'pages' => [
['url' => '/index.php?controller=Affiliate', 'name' => 'Affiliate Networks', 'desc' => 'Réseaux affiliés'],
['url' => '/index.php?controller=Offers', 'name' => 'Offers', 'desc' => 'Offres'],
['url' => '/index.php?controller=Conversions', 'name' => 'Conversions', 'desc' => 'Tracking conversions'],
]
],
'Administration' => [
'icon' => 'fa-cog',
'color' => '#64748b',
'pages' => [
['url' => '/index.php?controller=Users', 'name' => 'Users', 'desc' => 'Gestion utilisateurs'],
['url' => '/index.php?controller=Settings', 'name' => 'Settings', 'desc' => 'Paramètres'],
['url' => '/index.php?controller=Logs', 'name' => 'Logs', 'desc' => 'Journaux système'],
]
],
];
// =============================================
// SCAN DES PAGES STANDALONE
// =============================================
$excludeFiles = ['index.php', 'pages-index.php', '.htaccess'];
$standaloneCategories = [
'monitoring' => ['name' => 'Monitoring & Admin', 'icon' => 'fa-heartbeat', 'color' => '#22d3ee', 'patterns' => ['system-', 'monitoring', 'security', 'session', 'console', 'audit', 'whitelist']],
'huawei' => ['name' => 'Huawei Cloud', 'icon' => 'fa-cloud', 'color' => '#ef4444', 'patterns' => ['huawei-']],
'office' => ['name' => 'Office 365', 'icon' => 'fa-microsoft', 'color' => '#0078d4', 'patterns' => ['office-']],
'cloudflare' => ['name' => 'Cloudflare & DNS', 'icon' => 'fa-shield-alt', 'color' => '#f59e0b', 'patterns' => ['cloudflare-', 'domain', 'freedns', 'dkim', 'ptr', 'dns']],
'tracking' => ['name' => 'Tracking', 'icon' => 'fa-chart-line', 'color' => '#10b981', 'patterns' => ['tracking-']],
'chatbot' => ['name' => 'Chatbot & AI', 'icon' => 'fa-robot', 'color' => '#ec4899', 'patterns' => ['chatbot-', 'ai-', 'ollama']],
'database' => ['name' => 'Base de Données', 'icon' => 'fa-database', 'color' => '#06b6d4', 'patterns' => ['adminer', 'database', 'db-', 'sql', 'test-bd']],
's3' => ['name' => 'AWS S3', 'icon' => 'fa-aws', 'color' => '#ff9900', 'patterns' => ['s3_', 's3-', 'aws-']],
'tools' => ['name' => 'Outils', 'icon' => 'fa-tools', 'color' => '#64748b', 'patterns' => ['tool', 'util', 'helper', 'check', 'test', 'debug', 'error', 'phpinfo']],
'docs' => ['name' => 'Documentation', 'icon' => 'fa-book', 'color' => '#a855f7', 'patterns' => ['doc', 'architecture', 'help', 'guide', 'readme']],
'auth' => ['name' => 'Authentification', 'icon' => 'fa-lock', 'color' => '#eab308', 'patterns' => ['login', 'auth', 'logout', 'password']],
'demo' => ['name' => 'Démos', 'icon' => 'fa-play', 'color' => '#14b8a6', 'patterns' => ['demo', 'playground', 'sample']],
];
function scanStandaloneFiles($dir, $excludeFiles) {
$files = [];
if (!is_dir($dir)) return $files;
foreach (scandir($dir) as $item) {
if ($item[0] === '.' || in_array($item, $excludeFiles)) continue;
$path = $dir . '/' . $item;
if (is_file($path)) {
$ext = pathinfo($item, PATHINFO_EXTENSION);
if (in_array($ext, ['php', 'html'])) {
$stat = stat($path);
$files[] = [
'name' => $item,
'path' => $path,
'ext' => $ext,
'size' => filesize($path),
'created' => $stat['ctime'],
'modified' => $stat['mtime'],
];
}
}
}
return $files;
}
function categorizeFile($filename, $categories) {
$filename_lower = strtolower($filename);
foreach ($categories as $key => $cat) {
foreach ($cat['patterns'] as $pattern) {
if (strpos($filename_lower, strtolower($pattern)) !== false) {
return $key;
}
}
}
return 'other';
}
function checkUrlStatus($url) {
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_NOBODY => false,
CURLOPT_TIMEOUT => 5,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_MAXREDIRS => 3,
CURLOPT_SSL_VERIFYPEER => false,
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$error = curl_error($ch);
$totalTime = curl_getinfo($ch, CURLINFO_TOTAL_TIME);
curl_close($ch);
$status = 'unknown';
$comment = '';
if ($error) {
$status = 'error';
$comment = 'Connection failed: ' . $error;
} elseif ($httpCode == 200) {
$status = 'ok';
$comment = 'OK (' . round($totalTime * 1000) . 'ms)';
} elseif ($httpCode == 302 || $httpCode == 301) {
$status = 'redirect';
$comment = 'Redirect ' . $httpCode;
} elseif ($httpCode == 404) {
$status = 'error';
$comment = '404 - Page non trouvée';
} elseif ($httpCode == 500) {
$status = 'error';
$comment = '500 - Erreur serveur interne';
} elseif ($httpCode == 403) {
$status = 'warning';
$comment = '403 - Accès interdit';
} elseif ($httpCode == 401) {
$status = 'warning';
$comment = '401 - Auth requise';
} elseif ($httpCode >= 400) {
$status = 'error';
$comment = $httpCode . ' - Erreur client';
} elseif ($httpCode >= 500) {
$status = 'error';
$comment = $httpCode . ' - Erreur serveur';
} else {
$status = 'warning';
$comment = 'Code: ' . $httpCode;
}
return [
'code' => $httpCode,
'status' => $status,
'comment' => $comment,
'time' => round($totalTime * 1000),
];
}
// AJAX Handler
if (isset($_GET['action'])) {
header('Content-Type: application/json');
switch ($_GET['action']) {
case 'check_url':
$url = $_GET['url'] ?? '';
if ($url) {
$fullUrl = (strpos($url, 'http') === 0) ? $url : $baseUrl . $url;
echo json_encode(checkUrlStatus($fullUrl));
} else {
echo json_encode(['status' => 'error', 'comment' => 'No URL']);
}
break;
case 'check_all':
$results = [];
// Check native pages
foreach ($nativePages as $cat => $data) {
foreach ($data['pages'] as $page) {
$fullUrl = $baseUrl . $page['url'];
$results[$page['url']] = checkUrlStatus($fullUrl);
}
}
// Check standalone
$files = scanStandaloneFiles(__DIR__, $excludeFiles);
foreach ($files as $file) {
$fullUrl = $baseUrl . '/' . $file['name'];
$results['/' . $file['name']] = checkUrlStatus($fullUrl);
}
echo json_encode($results);
break;
case 'refresh_list':
$files = scanStandaloneFiles(__DIR__, $excludeFiles);
$categorized = [];
foreach ($standaloneCategories as $key => $cat) {
$categorized[$key] = ['info' => $cat, 'files' => []];
}
$categorized['other'] = ['info' => ['name' => 'Autres', 'icon' => 'fa-folder', 'color' => '#475569'], 'files' => []];
foreach ($files as $file) {
$catKey = categorizeFile($file['name'], $standaloneCategories);
$categorized[$catKey]['files'][] = $file;
}
echo json_encode($categorized);
break;
}
exit;
}
// Scan initial
$standaloneFiles = scanStandaloneFiles(__DIR__, $excludeFiles);
$categorizedStandalone = [];
foreach ($standaloneCategories as $key => $cat) {
$categorizedStandalone[$key] = [];
}
$categorizedStandalone['other'] = [];
foreach ($standaloneFiles as $file) {
$catKey = categorizeFile($file['name'], $standaloneCategories);
$categorizedStandalone[$catKey][] = $file;
}
$totalNative = array_sum(array_map(fn($c) => count($c['pages']), $nativePages));
$totalStandalone = count($standaloneFiles);
$lastRefresh = date('d/m/Y H:i:s');
?>
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WEVAL - Index Complet des Pages</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: 'Segoe UI', sans-serif; background: linear-gradient(135deg, #0f172a 0%, #1e293b 100%); min-height: 100vh; color: #e2e8f0; }
.header { background: linear-gradient(135deg, #0891b2, #0e7490); padding: 20px 30px; position: sticky; top: 0; z-index: 100; box-shadow: 0 4px 20px rgba(0,0,0,0.3); }
.header-content { max-width: 1800px; margin: 0 auto; display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 15px; }
.header h1 { font-size: 24px; display: flex; align-items: center; gap: 12px; }
.header-stats { display: flex; gap: 15px; flex-wrap: wrap; }
.stat-box { background: rgba(255,255,255,0.1); padding: 10px 20px; border-radius: 10px; text-align: center; }
.stat-number { font-size: 24px; font-weight: 700; color: #fff; }
.stat-label { font-size: 11px; color: rgba(255,255,255,0.7); margin-top: 2px; }
.header-actions { display: flex; gap: 10px; align-items: center; }
.search-input { padding: 10px 15px; border-radius: 8px; border: none; background: rgba(255,255,255,0.1); color: #fff; width: 200px; font-size: 13px; }
.search-input::placeholder { color: rgba(255,255,255,0.5); }
.search-input:focus { outline: none; background: rgba(255,255,255,0.2); }
.btn { padding: 10px 18px; border: none; border-radius: 8px; cursor: pointer; font-size: 13px; font-weight: 600; text-decoration: none; display: inline-flex; align-items: center; gap: 8px; transition: all 0.2s; }
.btn-primary { background: rgba(255,255,255,0.15); color: #fff; }
.btn-primary:hover { background: rgba(255,255,255,0.25); }
.btn-success { background: #10b981; color: #fff; }
.btn-success:hover { background: #059669; }
.btn-warning { background: #f59e0b; color: #fff; }
.btn-warning:hover { background: #d97706; }
.container { max-width: 1800px; margin: 0 auto; padding: 25px; }
.tabs { display: flex; gap: 5px; margin-bottom: 20px; background: rgba(0,0,0,0.2); padding: 5px; border-radius: 12px; }
.tab { flex: 1; padding: 15px; background: transparent; border: none; color: #94a3b8; font-size: 14px; font-weight: 600; cursor: pointer; border-radius: 8px; display: flex; align-items: center; justify-content: center; gap: 10px; transition: all 0.2s; }
.tab:hover { background: rgba(255,255,255,0.05); }
.tab.active { background: linear-gradient(135deg, #0891b2, #0e7490); color: #fff; }
.tab .badge { background: rgba(255,255,255,0.2); padding: 3px 10px; border-radius: 10px; font-size: 12px; }
.tab-content { display: none; }
.tab-content.active { display: block; }
.section { margin-bottom: 20px; }
.section-header { display: flex; align-items: center; gap: 12px; padding: 15px 20px; background: linear-gradient(135deg, #1e293b, #334155); border-radius: 12px 12px 0 0; border: 1px solid rgba(255,255,255,0.1); cursor: pointer; }
.section-header:hover { background: linear-gradient(135deg, #334155, #475569); }
.section-icon { width: 40px; height: 40px; border-radius: 10px; display: flex; align-items: center; justify-content: center; font-size: 18px; }
.section-title { font-size: 16px; font-weight: 600; flex: 1; }
.section-count { background: rgba(255,255,255,0.1); padding: 5px 15px; border-radius: 15px; font-size: 12px; }
.section-stats { display: flex; gap: 10px; }
.section-stat { padding: 4px 10px; border-radius: 8px; font-size: 11px; font-weight: 600; }
.section-stat.ok { background: rgba(16,185,129,0.2); color: #10b981; }
.section-stat.error { background: rgba(239,68,68,0.2); color: #ef4444; }
.section-stat.warning { background: rgba(245,158,11,0.2); color: #f59e0b; }
.section-content { background: rgba(15,23,42,0.5); border: 1px solid rgba(255,255,255,0.1); border-top: none; border-radius: 0 0 12px 12px; }
.page-table { width: 100%; border-collapse: collapse; }
.page-table th, .page-table td { padding: 12px 15px; text-align: left; border-bottom: 1px solid rgba(255,255,255,0.05); }
.page-table th { background: rgba(0,0,0,0.2); color: #94a3b8; font-size: 11px; font-weight: 600; text-transform: uppercase; }
.page-table tr:hover { background: rgba(6,182,212,0.05); }
.page-name { display: flex; align-items: center; gap: 10px; }
.page-icon { width: 32px; height: 32px; border-radius: 8px; display: flex; align-items: center; justify-content: center; font-size: 12px; }
.page-icon.php { background: linear-gradient(135deg, #8b5cf6, #7c3aed); }
.page-icon.html { background: linear-gradient(135deg, #f59e0b, #d97706); }
.page-icon.mvc { background: linear-gradient(135deg, #22d3ee, #0891b2); }
.page-title { font-weight: 600; color: #f8fafc; font-size: 13px; }
.page-desc { font-size: 11px; color: #64748b; margin-top: 2px; }
.page-url { font-family: monospace; font-size: 11px; color: #64748b; max-width: 300px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.status-cell { display: flex; align-items: center; gap: 8px; }
.status-dot { width: 10px; height: 10px; border-radius: 50%; flex-shrink: 0; }
.status-dot.ok { background: #10b981; box-shadow: 0 0 8px rgba(16,185,129,0.5); }
.status-dot.error { background: #ef4444; box-shadow: 0 0 8px rgba(239,68,68,0.5); }
.status-dot.warning { background: #f59e0b; box-shadow: 0 0 8px rgba(245,158,11,0.5); }
.status-dot.redirect { background: #3b82f6; box-shadow: 0 0 8px rgba(59,130,246,0.5); }
.status-dot.checking { background: #64748b; animation: pulse 1s infinite; }
.status-code { font-family: monospace; font-size: 12px; font-weight: 600; }
.status-code.ok { color: #10b981; }
.status-code.error { color: #ef4444; }
.status-code.warning { color: #f59e0b; }
.status-code.redirect { color: #3b82f6; }
.status-comment { font-size: 11px; color: #64748b; }
.date-cell { font-size: 11px; color: #64748b; }
.date-new { background: #ef4444; color: #fff; font-size: 9px; padding: 2px 6px; border-radius: 4px; margin-left: 5px; }
.actions-cell { display: flex; gap: 5px; }
.action-btn { width: 30px; height: 30px; border-radius: 6px; border: none; cursor: pointer; display: flex; align-items: center; justify-content: center; font-size: 11px; transition: all 0.2s; }
.action-btn.open { background: #10b981; color: #fff; }
.action-btn.open:hover { background: #059669; }
.action-btn.copy { background: #3b82f6; color: #fff; }
.action-btn.copy:hover { background: #2563eb; }
.action-btn.new-tab { background: #8b5cf6; color: #fff; }
.action-btn.new-tab:hover { background: #7c3aed; }
.action-btn.check { background: #f59e0b; color: #fff; }
.action-btn.check:hover { background: #d97706; }
.progress-bar { position: fixed; top: 0; left: 0; height: 3px; background: linear-gradient(90deg, #22d3ee, #10b981); width: 0; z-index: 1000; transition: width 0.3s; }
.toast { position: fixed; bottom: 20px; right: 20px; background: #10b981; color: #fff; padding: 15px 25px; border-radius: 10px; font-size: 13px; display: none; z-index: 1000; box-shadow: 0 10px 30px rgba(0,0,0,0.3); }
.toast.show { display: flex; align-items: center; gap: 10px; animation: slideIn 0.3s ease; }
.toast.error { background: #ef4444; }
@keyframes slideIn { from { transform: translateX(100px); opacity: 0; } to { transform: translateX(0); opacity: 1; } }
@keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.4; } }
.summary-bar { display: grid; grid-template-columns: repeat(5, 1fr); gap: 15px; margin-bottom: 20px; }
.summary-item { background: linear-gradient(135deg, #1e293b, #334155); padding: 20px; border-radius: 12px; border: 1px solid rgba(255,255,255,0.1); }
.summary-icon { width: 50px; height: 50px; border-radius: 12px; display: flex; align-items: center; justify-content: center; font-size: 22px; margin-bottom: 10px; }
.summary-value { font-size: 28px; font-weight: 700; }
.summary-label { font-size: 12px; color: #64748b; margin-top: 5px; }
.loading-overlay { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(15,23,42,0.9); display: none; align-items: center; justify-content: center; z-index: 2000; flex-direction: column; gap: 20px; }
.loading-overlay.show { display: flex; }
.loading-spinner { width: 50px; height: 50px; border: 4px solid rgba(255,255,255,0.1); border-top-color: #22d3ee; border-radius: 50%; animation: spin 1s linear infinite; }
@keyframes spin { to { transform: rotate(360deg); } }
.loading-text { font-size: 16px; color: #94a3b8; }
.loading-progress { font-size: 14px; color: #22d3ee; }
@media (max-width: 1200px) {
.summary-bar { grid-template-columns: repeat(3, 1fr); }
.header-content { flex-direction: column; }
}
@media (max-width: 768px) {
.summary-bar { grid-template-columns: 1fr 1fr; }
.tabs { flex-direction: column; }
.page-url { display: none; }
}
</style>
</head>
<body>
<div class="progress-bar" id="progressBar"></div>
<div class="loading-overlay" id="loadingOverlay">
<div class="loading-spinner"></div>
<div class="loading-text">Vérification des pages en cours...</div>
<div class="loading-progress" id="loadingProgress">0 / 0</div>
</div>
<div class="header">
<div class="header-content">
<h1><i class="fas fa-sitemap"></i> WEVAL - Index Complet</h1>
<div class="header-stats">
<div class="stat-box">
<div class="stat-number"><?= $totalNative ?></div>
<div class="stat-label">PAGES NATIVES</div>
</div>
<div class="stat-box">
<div class="stat-number"><?= $totalStandalone ?></div>
<div class="stat-label">STANDALONE</div>
</div>
<div class="stat-box">
<div class="stat-number" id="okCount">-</div>
<div class="stat-label">OK</div>
</div>
<div class="stat-box">
<div class="stat-number" id="errorCount">-</div>
<div class="stat-label">ERREURS</div>
</div>
</div>
<div class="header-actions">
<input type="text" class="search-input" id="searchInput" placeholder="Rechercher...">
<button class="btn btn-success" onclick="checkAllPages()"><i class="fas fa-sync-alt"></i> Vérifier Tout</button>
<button class="btn btn-warning" onclick="refreshList()"><i class="fas fa-redo"></i> Actualiser</button>
<a href="dashboard.html" class="btn btn-primary"><i class="fas fa-home"></i> Dashboard</a>
</div>
</div>
</div>
<div class="container">
<!-- Summary -->
<div class="summary-bar">
<div class="summary-item">
<div class="summary-icon" style="background:rgba(34,211,238,0.2);color:#22d3ee"><i class="fas fa-file-code"></i></div>
<div class="summary-value"><?= $totalNative + $totalStandalone ?></div>
<div class="summary-label">Total Pages</div>
</div>
<div class="summary-item">
<div class="summary-icon" style="background:rgba(16,185,129,0.2);color:#10b981"><i class="fas fa-check-circle"></i></div>
<div class="summary-value" id="summaryOk">-</div>
<div class="summary-label">Fonctionnelles</div>
</div>
<div class="summary-item">
<div class="summary-icon" style="background:rgba(239,68,68,0.2);color:#ef4444"><i class="fas fa-times-circle"></i></div>
<div class="summary-value" id="summaryError">-</div>
<div class="summary-label">En Erreur</div>
</div>
<div class="summary-item">
<div class="summary-icon" style="background:rgba(245,158,11,0.2);color:#f59e0b"><i class="fas fa-exclamation-circle"></i></div>
<div class="summary-value" id="summaryWarning">-</div>
<div class="summary-label">Avertissements</div>
</div>
<div class="summary-item">
<div class="summary-icon" style="background:rgba(139,92,246,0.2);color:#8b5cf6"><i class="fas fa-clock"></i></div>
<div class="summary-value" id="lastCheck">-</div>
<div class="summary-label">Dernière vérif.</div>
</div>
</div>
<!-- Tabs -->
<div class="tabs">
<button class="tab active" onclick="switchTab('native')">
<i class="fas fa-window-maximize"></i> Pages Natives (MVC)
<span class="badge"><?= $totalNative ?></span>
</button>
<button class="tab" onclick="switchTab('standalone')">
<i class="fas fa-file-code"></i> Pages Standalone
<span class="badge"><?= $totalStandalone ?></span>
</button>
<button class="tab" onclick="switchTab('errors')">
<i class="fas fa-exclamation-triangle"></i> Erreurs
<span class="badge" id="errorTabBadge">0</span>
</button>
</div>
<!-- Native Pages Tab -->
<div class="tab-content active" id="tab-native">
<?php foreach ($nativePages as $catName => $catData): ?>
<div class="section" data-category="<?= htmlspecialchars($catName) ?>">
<div class="section-header">
<div class="section-icon" style="background: <?= $catData['color'] ?>20; color: <?= $catData['color'] ?>">
<i class="fas <?= $catData['icon'] ?>"></i>
</div>
<div class="section-title"><?= htmlspecialchars($catName) ?></div>
<div class="section-count"><?= count($catData['pages']) ?> pages</div>
<div class="section-stats" id="stats-native-<?= md5($catName) ?>"></div>
</div>
<div class="section-content">
<table class="page-table">
<thead>
<tr>
<th>Page</th>
<th>URL</th>
<th>Status</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<?php foreach ($catData['pages'] as $page): ?>
<tr data-url="<?= htmlspecialchars($page['url']) ?>">
<td>
<div class="page-name">
<div class="page-icon mvc"><i class="fas fa-code"></i></div>
<div>
<div class="page-title"><?= htmlspecialchars($page['name']) ?></div>
<div class="page-desc"><?= htmlspecialchars($page['desc']) ?></div>
</div>
</div>
</td>
<td><span class="page-url"><?= htmlspecialchars($page['url']) ?></span></td>
<td>
<div class="status-cell" id="status-<?= md5($page['url']) ?>">
<span class="status-dot checking"></span>
<span class="status-code">...</span>
<span class="status-comment">En attente</span>
</div>
</td>
<td>
<div class="actions-cell">
<a href="<?= htmlspecialchars($page['url']) ?>" class="action-btn open" title="Ouvrir"><i class="fas fa-external-link-alt"></i></a>
<button class="action-btn copy" onclick="copyUrl('<?= htmlspecialchars($page['url']) ?>')" title="Copier"><i class="fas fa-copy"></i></button>
<a href="<?= htmlspecialchars($page['url']) ?>" target="_blank" class="action-btn new-tab" title="Nouvel onglet"><i class="fas fa-plus"></i></a>
<button class="action-btn check" onclick="checkSingleUrl('<?= htmlspecialchars($page['url']) ?>')" title="Vérifier"><i class="fas fa-sync"></i></button>
</div>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
<?php endforeach; ?>
</div>
<!-- Standalone Pages Tab -->
<div class="tab-content" id="tab-standalone">
<?php
$allCategories = $standaloneCategories;
$allCategories['other'] = ['name' => 'Autres', 'icon' => 'fa-folder', 'color' => '#475569'];
foreach ($allCategories as $catKey => $catInfo):
if (empty($categorizedStandalone[$catKey])) continue;
?>
<div class="section" data-category="<?= $catKey ?>">
<div class="section-header">
<div class="section-icon" style="background: <?= $catInfo['color'] ?>20; color: <?= $catInfo['color'] ?>">
<i class="fas <?= $catInfo['icon'] ?>"></i>
</div>
<div class="section-title"><?= htmlspecialchars($catInfo['name']) ?></div>
<div class="section-count"><?= count($categorizedStandalone[$catKey]) ?> fichiers</div>
<div class="section-stats" id="stats-standalone-<?= $catKey ?>"></div>
</div>
<div class="section-content">
<table class="page-table">
<thead>
<tr>
<th>Fichier</th>
<th>URL</th>
<th>Créé le</th>
<th>Status</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<?php foreach ($categorizedStandalone[$catKey] as $file):
$isNew = (time() - $file['created']) < 86400;
$url = '/' . $file['name'];
?>
<tr data-url="<?= htmlspecialchars($url) ?>">
<td>
<div class="page-name">
<div class="page-icon <?= $file['ext'] ?>"><i class="fas <?= $file['ext'] === 'php' ? 'fa-php' : 'fa-code' ?>"></i></div>
<div>
<div class="page-title"><?= htmlspecialchars($file['name']) ?></div>
<div class="page-desc"><?= round($file['size']/1024, 1) ?> KB</div>
</div>
</div>
</td>
<td><span class="page-url"><?= htmlspecialchars($url) ?></span></td>
<td>
<div class="date-cell">
<?= date('d/m/Y H:i', $file['created']) ?>
<?php if ($isNew): ?><span class="date-new">NEW</span><?php endif; ?>
</div>
</td>
<td>
<div class="status-cell" id="status-<?= md5($url) ?>">
<span class="status-dot checking"></span>
<span class="status-code">...</span>
<span class="status-comment">En attente</span>
</div>
</td>
<td>
<div class="actions-cell">
<a href="<?= htmlspecialchars($url) ?>" class="action-btn open" title="Ouvrir"><i class="fas fa-external-link-alt"></i></a>
<button class="action-btn copy" onclick="copyUrl('<?= htmlspecialchars($url) ?>')" title="Copier"><i class="fas fa-copy"></i></button>
<a href="<?= htmlspecialchars($url) ?>" target="_blank" class="action-btn new-tab" title="Nouvel onglet"><i class="fas fa-plus"></i></a>
<button class="action-btn check" onclick="checkSingleUrl('<?= htmlspecialchars($url) ?>')" title="Vérifier"><i class="fas fa-sync"></i></button>
</div>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
<?php endforeach; ?>
</div>
<!-- Errors Tab -->
<div class="tab-content" id="tab-errors">
<div class="section">
<div class="section-header">
<div class="section-icon" style="background: rgba(239,68,68,0.2); color: #ef4444">
<i class="fas fa-exclamation-triangle"></i>
</div>
<div class="section-title">Pages en Erreur</div>
<div class="section-count" id="errorListCount">0 erreurs</div>
</div>
<div class="section-content">
<table class="page-table">
<thead>
<tr>
<th>Page</th>
<th>URL</th>
<th>Code</th>
<th>Erreur</th>
<th>Actions</th>
</tr>
</thead>
<tbody id="errorTableBody">
<tr><td colspan="5" style="text-align:center;color:#64748b;padding:30px;">Lancez une vérification pour voir les erreurs</td></tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
<div class="toast" id="toast"><i class="fas fa-check"></i> <span id="toastText">Message</span></div>
<script>
const baseUrl = '<?= $baseUrl ?>';
let allUrls = [];
let statusResults = {};
// Collecter toutes les URLs
document.querySelectorAll('tr[data-url]').forEach(row => {
allUrls.push(row.dataset.url);
});
function switchTab(tab) {
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
document.querySelectorAll('.tab-content').forEach(c => c.classList.remove('active'));
document.querySelector(`.tab:nth-child(${tab === 'native' ? 1 : tab === 'standalone' ? 2 : 3})`).classList.add('active');
document.getElementById('tab-' + tab).classList.add('active');
}
function showToast(msg, isError = false) {
const toast = document.getElementById('toast');
document.getElementById('toastText').textContent = msg;
toast.className = 'toast show' + (isError ? ' error' : '');
setTimeout(() => toast.className = 'toast', 3000);
}
function copyUrl(url) {
navigator.clipboard.writeText(baseUrl + url).then(() => {
showToast('URL copiée: ' + url);
});
}
function updateStatusCell(url, data) {
const cell = document.getElementById('status-' + MD5(url));
if (!cell) return;
cell.innerHTML = `
<span class="status-dot ${data.status}"></span>
<span class="status-code ${data.status}">${data.code || '?'}</span>
<span class="status-comment">${data.comment}</span>
`;
statusResults[url] = data;
}
async function checkSingleUrl(url) {
const cell = document.getElementById('status-' + MD5(url));
if (cell) {
cell.innerHTML = '<span class="status-dot checking"></span><span class="status-code">...</span><span class="status-comment">Vérification...</span>';
}
try {
const response = await fetch(`?action=check_url&url=${encodeURIComponent(url)}`);
const data = await response.json();
updateStatusCell(url, data);
} catch (e) {
updateStatusCell(url, { status: 'error', code: 0, comment: 'Erreur réseau' });
}
}
async function checkAllPages() {
const overlay = document.getElementById('loadingOverlay');
const progress = document.getElementById('loadingProgress');
const progressBar = document.getElementById('progressBar');
overlay.classList.add('show');
let checked = 0;
const total = allUrls.length;
for (const url of allUrls) {
try {
const response = await fetch(`?action=check_url&url=${encodeURIComponent(url)}`);
const data = await response.json();
updateStatusCell(url, data);
} catch (e) {
updateStatusCell(url, { status: 'error', code: 0, comment: 'Erreur réseau' });
}
checked++;
progress.textContent = `${checked} / ${total}`;
progressBar.style.width = (checked / total * 100) + '%';
await new Promise(r => setTimeout(r, 50));
}
overlay.classList.remove('show');
progressBar.style.width = '0';
updateSummary();
updateErrorsTab();
showToast(`Vérification terminée: ${checked} pages`);
}
function updateSummary() {
let ok = 0, error = 0, warning = 0;
for (const url in statusResults) {
const status = statusResults[url].status;
if (status === 'ok' || status === 'redirect') ok++;
else if (status === 'error') error++;
else if (status === 'warning') warning++;
}
document.getElementById('okCount').textContent = ok;
document.getElementById('errorCount').textContent = error;
document.getElementById('summaryOk').textContent = ok;
document.getElementById('summaryError').textContent = error;
document.getElementById('summaryWarning').textContent = warning;
document.getElementById('errorTabBadge').textContent = error;
document.getElementById('lastCheck').textContent = new Date().toLocaleTimeString('fr-FR', { hour: '2-digit', minute: '2-digit' });
}
function updateErrorsTab() {
const tbody = document.getElementById('errorTableBody');
const errors = Object.entries(statusResults).filter(([url, data]) => data.status === 'error' || data.status === 'warning');
if (errors.length === 0) {
tbody.innerHTML = '<tr><td colspan="5" style="text-align:center;color:#10b981;padding:30px;"><i class="fas fa-check-circle"></i> Aucune erreur détectée!</td></tr>';
document.getElementById('errorListCount').textContent = '0 erreurs';
return;
}
document.getElementById('errorListCount').textContent = errors.length + ' erreurs';
tbody.innerHTML = errors.map(([url, data]) => `
<tr>
<td><div class="page-title">${url.split('/').pop() || url}</div></td>
<td><span class="page-url">${url}</span></td>
<td><span class="status-code ${data.status}">${data.code}</span></td>
<td><span class="status-comment">${data.comment}</span></td>
<td>
<div class="actions-cell">
<a href="${url}" class="action-btn open"><i class="fas fa-external-link-alt"></i></a>
<button class="action-btn check" onclick="checkSingleUrl('${url}')"><i class="fas fa-sync"></i></button>
</div>
</td>
</tr>
`).join('');
}
function refreshList() {
location.reload();
}
// Search
document.getElementById('searchInput').addEventListener('input', function(e) {
const query = e.target.value.toLowerCase();
document.querySelectorAll('tr[data-url]').forEach(row => {
const url = row.dataset.url.toLowerCase();
const text = row.textContent.toLowerCase();
row.style.display = (url.includes(query) || text.includes(query)) ? '' : 'none';
});
});
// Simple MD5 hash for element IDs
function MD5(s){function L(k,d){return(k<<d)|(k>>>(32-d))}function K(G,k){var I,d,F,H,x;F=(G&2147483648);H=(k&2147483648);I=(G&1073741824);d=(k&1073741824);x=(G&1073741823)+(k&1073741823);if(I&d){return(x^2147483648^F^H)}if(I|d){if(x&1073741824){return(x^3221225472^F^H)}else{return(x^1073741824^F^H)}}else{return(x^F^H)}}function r(d,F,k){return(d&F)|((~d)&k)}function q(d,F,k){return(d&k)|(F&(~k))}function p(d,F,k){return(d^F^k)}function n(d,F,k){return(F^(d|(~k)))}function u(G,F,aa,Z,k,H,I){G=K(G,K(K(r(F,aa,Z),k),I));return K(L(G,H),F)}function f(G,F,aa,Z,k,H,I){G=K(G,K(K(q(F,aa,Z),k),I));return K(L(G,H),F)}function D(G,F,aa,Z,k,H,I){G=K(G,K(K(p(F,aa,Z),k),I));return K(L(G,H),F)}function t(G,F,aa,Z,k,H,I){G=K(G,K(K(n(F,aa,Z),k),I));return K(L(G,H),F)}function e(G){var Z;var F=G.length;var x=F+8;var k=(x-(x%64))/64;var I=(k+1)*16;var aa=Array(I-1);var d=0;var H=0;while(H<F){Z=(H-(H%4))/4;d=(H%4)*8;aa[Z]=(aa[Z]}|(G.charCodeAt(H)<<d));H++}Z=(H-(H%4))/4;d=(H%4)*8;aa[Z]=aa[Z]|(128<<d);aa[I-2]=F<<3;aa[I-1]=F>>>29;return aa}function B(x){var k="",F="",G,d;for(d=0;d<=3;d++){G=(x>>>(d*8))&255;F="0"+G.toString(16);k=k+F.substr(F.length-2,2)}return k}function J(k){k=k.replace(/\r\n/g,"\n");var d="";for(var F=0;F<k.length;F++){var x=k.charCodeAt(F);if(x<128){d+=String.fromCharCode(x)}else{if((x>127)&&(x<2048)){d+=String.fromCharCode((x>>6)|192);d+=String.fromCharCode((x&63)|128)}else{d+=String.fromCharCode((x>>12)}224);d+=String.fromCharCode(((x>>6)&63)|128);d+=String.fromCharCode((x&63)|128)}}}return d}var C=Array();var P,h,E,v,g,Y,X,W,V;var S=7,Q=12,N=17,M=22;var A=5,z=9,y=14,w=20;var o=4,m=11,l=16,j=23;var U=6,T=10,R=15,O=21;s=J(s);C=e(s);Y=1732584193;X=4023233417;W=2562383102;V=271733878;for(P=0;P<C.length;P+=16){h=Y;E=X;v=W;g=V;Y=u(Y,X,W,V,C[P+0],S,3614090360);V=u(V,Y,X,W,C[P+1],Q,3905402710);W=u(W,V,Y,X,C[P+2],N,606105819);X=u(X,W,V,Y,C[P+3],M,3250441966);Y=u(Y,X,W,V,C[P+4],S,4118548399);V=u(V,Y,X,W,C[P+5],Q,1200080426);W=u(W,V,Y,X,C[P+6],N,2821735955);X=u(X,W,V,Y,C[P+7],M,4249261313);Y=u(Y,X,W,V,C[P+8],S,1770035416);V=u(V,Y,X,W,C[P+9],Q,2336552879);W=u(W,V,Y,X,C[P+10],N,4294925233);X=u(X,W,V,Y,C[P+11],M,2304563134);Y=u(Y,X,W,V,C[P+12],S,1804603682);V=u(V,Y,X,W,C[P+13],Q,4254626195);W=u(W,V,Y,X,C[P+14],N,2792965006);X=u(X,W,V,Y,C[P+15],M,1236535329);Y=f(Y,X,W,V,C[P+1],A,4129170786);V=f(V,Y,X,W,C[P+6],z,3225465664);W=f(W,V,Y,X,C[P+11],y,643717713);X=f(X,W,V,Y,C[P+0],w,3921069994);Y=f(Y,X,W,V,C[P+5],A,3593408605);V=f(V,Y,X,W,C[P+10],z,38016083);W=f(W,V,Y,X,C[P+15],y,3634488961);X=f(X,W,V,Y,C[P+4],w,3889429448);Y=f(Y,X,W,V,C[P+9],A,568446438);V=f(V,Y,X,W,C[P+14],z,3275163606);W=f(W,V,Y,X,C[P+3],y,4107603335);X=f(X,W,V,Y,C[P+8],w,1163531501);Y=f(Y,X,W,V,C[P+13],A,2850285829);V=f(V,Y,X,W,C[P+2],z,4243563512);W=f(W,V,Y,X,C[P+7],y,1735328473);X=f(X,W,V,Y,C[P+12],w,2368359562);Y=D(Y,X,W,V,C[P+5],o,4294588738);V=D(V,Y,X,W,C[P+8],m,2272392833);W=D(W,V,Y,X,C[P+11],l,1839030562);X=D(X,W,V,Y,C[P+14],j,4259657740);Y=D(Y,X,W,V,C[P+1],o,2763975236);V=D(V,Y,X,W,C[P+4],m,1272893353);W=D(W,V,Y,X,C[P+7],l,4139469664);X=D(X,W,V,Y,C[P+10],j,3200236656);Y=D(Y,X,W,V,C[P+13],o,681279174);V=D(V,Y,X,W,C[P+0],m,3936430074);W=D(W,V,Y,X,C[P+3],l,3572445317);X=D(X,W,V,Y,C[P+6],j,76029189);Y=D(Y,X,W,V,C[P+9],o,3654602809);V=D(V,Y,X,W,C[P+12],m,3873151461);W=D(W,V,Y,X,C[P+15],l,530742520);X=D(X,W,V,Y,C[P+2],j,3299628645);Y=t(Y,X,W,V,C[P+0],U,4096336452);V=t(V,Y,X,W,C[P+7],T,1126891415);W=t(W,V,Y,X,C[P+14],R,2878612391);X=t(X,W,V,Y,C[P+5],O,4237533241);Y=t(Y,X,W,V,C[P+12],U,1700485571);V=t(V,Y,X,W,C[P+3],T,2399980690);W=t(W,V,Y,X,C[P+10],R,4293915773);X=t(X,W,V,Y,C[P+1],O,2240044497);Y=t(Y,X,W,V,C[P+8],U,1873313359);V=t(V,Y,X,W,C[P+15],T,4264355552);W=t(W,V,Y,X,C[P+6],R,2734768916);X=t(X,W,V,Y,C[P+13],O,1309151649);Y=t(Y,X,W,V,C[P+4],U,4149444226);V=t(V,Y,X,W,C[P+11],T,3174756917);W=t(W,V,Y,X,C[P+2],R,718787259);X=t(X,W,V,Y,C[P+9],O,3951481745);Y=K(Y,h);X=K(X,E);W=K(W,v);V=K(V,g)}return(B(Y)+B(X)+B(W)+B(V)).toLowerCase()}
// Auto-check on load (first 20)
document.addEventListener('DOMContentLoaded', () => {
const first20 = allUrls.slice(0, 20);
first20.forEach((url, i) => {
setTimeout(() => checkSingleUrl(url), i * 100);
});
setTimeout(updateSummary, first20.length * 100 + 500);
});
</script>
<?php include("includes/chatbot-widget.php"); ?>
</body>
</html>