226 lines
8.7 KiB
PHP
226 lines
8.7 KiB
PHP
<?php
|
|
/**
|
|
* admin/import_history.php
|
|
*
|
|
* Historique des imports .ntm et de leurs erreurs. Lecture seule sauf
|
|
* pour la purge optionnelle des lignes d'erreur d'un import, action
|
|
* protegee par CSRF + confirmation explicite.
|
|
*
|
|
* CORRIGE (20/09/2026, suite a erreur 500 en usage reel) :
|
|
* - require_once _nav.php manquait, provoquant une erreur fatale
|
|
* "Call to undefined function render_admin_nav()".
|
|
* - Les tables import_batches / import_errors necessaires a cette
|
|
* page n'existaient pas : voir
|
|
* admin/sql/migration_add_missing_tables.sql.
|
|
*
|
|
* IMPORTANT : import_batches/import_errors sont DISTINCTES de la table
|
|
* `imports` deja existante et alimentee par lib/ntm_import_to_staging.php
|
|
* (utilisee par admin/import_ntm.php). Tant qu'aucune page n'insere
|
|
* explicitement dans import_batches/import_errors, cette page
|
|
* s'affichera correctement mais restera VIDE ("Aucun import pour le
|
|
* moment"), meme si vous avez deja importe des fichiers .ntm via
|
|
* import_ntm.php. Une evolution future pourrait brancher cette page
|
|
* directement sur la table `imports` existante plutot que de dupliquer
|
|
* un systeme de suivi parallele jamais alimente.
|
|
*
|
|
* Corrige suite a l'audit du 20/09/2026 : le detail technique des
|
|
* exceptions n'est plus affiche a l'ecran ; il est journalise via
|
|
* error_log().
|
|
*
|
|
* Hypothese de schema :
|
|
* import_batches(id, filename, imported_at, imported_by, status,
|
|
* rows_total, rows_imported, rows_rejected, country_id)
|
|
* import_errors(id, batch_id, line_number, message, raw_line)
|
|
*/
|
|
declare(strict_types=1);
|
|
|
|
require_once __DIR__ . '/admin_auth.php';
|
|
require_once __DIR__ . '/../lib/db.php';
|
|
require_once __DIR__ . '/_nav.php';
|
|
|
|
require_admin_auth();
|
|
|
|
$db = pdo();
|
|
$feedback = null;
|
|
$feedbackType = 'success';
|
|
|
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|
require_csrf_token();
|
|
|
|
$action = $_POST['action'] ?? '';
|
|
$confirmed = isset($_POST['confirm']) && $_POST['confirm'] === '1';
|
|
$batchId = filter_input(INPUT_POST, 'batch_id', FILTER_VALIDATE_INT);
|
|
|
|
if ($action === 'purge_errors') {
|
|
if (!$confirmed || !$batchId) {
|
|
$feedback = 'Purge refusee : confirmation ou identifiant manquant.';
|
|
$feedbackType = 'error';
|
|
} else {
|
|
try {
|
|
$stmt = $db->prepare('DELETE FROM import_errors WHERE batch_id = ?');
|
|
$stmt->execute([$batchId]);
|
|
$feedback = sprintf('%d ligne(s) d\'erreur purgee(s) pour l\'import #%d.', $stmt->rowCount(), $batchId);
|
|
} catch (Throwable $e) {
|
|
error_log('[import_history] purge_errors: ' . $e->getMessage());
|
|
$feedback = 'Une erreur est survenue lors de la purge.';
|
|
$feedbackType = 'error';
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
$page = max(1, filter_input(INPUT_GET, 'page', FILTER_VALIDATE_INT) ?: 1);
|
|
$perPage = 25;
|
|
$offset = ($page - 1) * $perPage;
|
|
|
|
$totalBatches = 0;
|
|
$batches = [];
|
|
try {
|
|
$totalBatches = (int)$db->query('SELECT COUNT(*) FROM import_batches')->fetchColumn();
|
|
|
|
$stmt = $db->prepare(
|
|
'SELECT id, filename, imported_at, imported_by, status, rows_total, rows_imported, rows_rejected
|
|
FROM import_batches
|
|
ORDER BY imported_at DESC
|
|
LIMIT :limit OFFSET :offset'
|
|
);
|
|
$stmt->bindValue(':limit', $perPage, PDO::PARAM_INT);
|
|
$stmt->bindValue(':offset', $offset, PDO::PARAM_INT);
|
|
$stmt->execute();
|
|
$batches = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|
} catch (Throwable $e) {
|
|
error_log('[import_history] liste batches: ' . $e->getMessage());
|
|
$feedback = 'Impossible de charger l\'historique des imports. Consultez les journaux du serveur.';
|
|
$feedbackType = 'error';
|
|
}
|
|
|
|
$errorsByBatch = [];
|
|
$detailBatchId = filter_input(INPUT_GET, 'detail', FILTER_VALIDATE_INT);
|
|
if ($detailBatchId) {
|
|
try {
|
|
$stmt = $db->prepare(
|
|
'SELECT line_number, message, raw_line
|
|
FROM import_errors
|
|
WHERE batch_id = ?
|
|
ORDER BY line_number ASC
|
|
LIMIT 500'
|
|
);
|
|
$stmt->execute([$detailBatchId]);
|
|
$errorsByBatch = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|
} catch (Throwable $e) {
|
|
error_log('[import_history] detail erreurs: ' . $e->getMessage());
|
|
}
|
|
}
|
|
|
|
$totalPages = max(1, (int)ceil($totalBatches / $perPage));
|
|
$user = current_admin_username();
|
|
|
|
function status_badge(string $status): string
|
|
{
|
|
$map = [
|
|
'success' => 'badge-success',
|
|
'partial' => 'badge-warning',
|
|
'failed' => 'badge-error',
|
|
];
|
|
$class = $map[$status] ?? 'badge';
|
|
return '<span class="badge ' . $class . '">' . htmlspecialchars($status, ENT_QUOTES, 'UTF-8') . '</span>';
|
|
}
|
|
?>
|
|
<!DOCTYPE html>
|
|
<html lang="fr">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<title>Historique des imports — NetMonster Admin</title>
|
|
<link rel="stylesheet" href="assets/theme.css">
|
|
</head>
|
|
<body>
|
|
<?php render_admin_nav('import_history.php', $user); ?>
|
|
|
|
<main class="page-wrap">
|
|
<h1>Historique des imports .ntm</h1>
|
|
<p class="page-subtitle">Suivi en lecture seule des imports realises. La purge des erreurs d'un import est possible mais n'affecte jamais les donnees deja validees.</p>
|
|
|
|
<?php if ($feedback !== null): ?>
|
|
<p class="<?= $feedbackType === 'error' ? 'msg-err' : 'msg-ok' ?>">
|
|
<?= htmlspecialchars($feedback, ENT_QUOTES, 'UTF-8') ?>
|
|
</p>
|
|
<?php endif; ?>
|
|
|
|
<section class="card">
|
|
<h2>Imports (<?= $totalBatches ?>)</h2>
|
|
<table class="data-table">
|
|
<tr>
|
|
<th>#</th>
|
|
<th>Fichier</th>
|
|
<th>Date</th>
|
|
<th>Utilisateur</th>
|
|
<th>Statut</th>
|
|
<th>Lignes totales</th>
|
|
<th>Importees</th>
|
|
<th>Rejetees</th>
|
|
<th>Actions</th>
|
|
</tr>
|
|
<?php foreach ($batches as $batch): ?>
|
|
<tr>
|
|
<td><?= (int)$batch['id'] ?></td>
|
|
<td><?= htmlspecialchars((string)$batch['filename'], ENT_QUOTES, 'UTF-8') ?></td>
|
|
<td><?= htmlspecialchars((string)$batch['imported_at'], ENT_QUOTES, 'UTF-8') ?></td>
|
|
<td><?= htmlspecialchars((string)$batch['imported_by'], ENT_QUOTES, 'UTF-8') ?></td>
|
|
<td><?= status_badge((string)$batch['status']) ?></td>
|
|
<td><?= (int)$batch['rows_total'] ?></td>
|
|
<td><?= (int)$batch['rows_imported'] ?></td>
|
|
<td><?= (int)$batch['rows_rejected'] ?></td>
|
|
<td>
|
|
<a href="import_history.php?detail=<?= (int)$batch['id'] ?>">Voir erreurs</a>
|
|
<?php if ((int)$batch['rows_rejected'] > 0): ?>
|
|
<form method="post" style="display:inline" onsubmit="return confirm('Confirmer la purge des erreurs de cet import ?');">
|
|
<?= csrf_field() ?>
|
|
<input type="hidden" name="action" value="purge_errors">
|
|
<input type="hidden" name="confirm" value="1">
|
|
<input type="hidden" name="batch_id" value="<?= (int)$batch['id'] ?>">
|
|
<button type="submit" class="reject-small">Purger erreurs</button>
|
|
</form>
|
|
<?php endif; ?>
|
|
</td>
|
|
</tr>
|
|
<?php endforeach; ?>
|
|
<?php if (empty($batches)): ?>
|
|
<tr><td colspan="9" class="no-cadastre">Aucun import pour le moment.</td></tr>
|
|
<?php endif; ?>
|
|
</table>
|
|
|
|
<?php if ($totalPages > 1): ?>
|
|
<nav class="pagination">
|
|
<?php for ($p = 1; $p <= $totalPages; $p++): ?>
|
|
<a href="import_history.php?page=<?= $p ?>" class="<?= $p === $page ? 'active' : '' ?>"><?= $p ?></a>
|
|
<?php endfor; ?>
|
|
</nav>
|
|
<?php endif; ?>
|
|
</section>
|
|
|
|
<?php if ($detailBatchId): ?>
|
|
<section class="card">
|
|
<h2>Erreurs de l'import #<?= (int)$detailBatchId ?></h2>
|
|
<?php if (empty($errorsByBatch)): ?>
|
|
<p>Aucune erreur enregistree pour cet import.</p>
|
|
<?php else: ?>
|
|
<table class="data-table">
|
|
<tr>
|
|
<th>Ligne</th>
|
|
<th>Message</th>
|
|
<th>Contenu brut</th>
|
|
</tr>
|
|
<?php foreach ($errorsByBatch as $err): ?>
|
|
<tr>
|
|
<td><?= (int)$err['line_number'] ?></td>
|
|
<td><?= htmlspecialchars((string)$err['message'], ENT_QUOTES, 'UTF-8') ?></td>
|
|
<td><code><?= htmlspecialchars((string)$err['raw_line'], ENT_QUOTES, 'UTF-8') ?></code></td>
|
|
</tr>
|
|
<?php endforeach; ?>
|
|
</table>
|
|
<?php endif; ?>
|
|
</section>
|
|
<?php endif; ?>
|
|
</main>
|
|
</body>
|
|
</html>
|