This commit introduces several utility functions for database synchronization, including error handling for MySQL operations and retrieval of table metadata. The `fxStaticSyncDbLastError`, `fxStaticSyncExecQuery`, `fxStaticSyncGetTableColumnNames`, and `fxStaticSyncGetTableEngine` functions are added to improve the robustness of the synchronization process. Additionally, the `fxStaticSyncCopyTable` function is updated to handle column mismatches more gracefully and to provide clearer feedback on the synchronization results, enhancing overall user experience and error reporting in the database synchronization workflow.
224 lines
9.7 KiB
PHP
224 lines
9.7 KiB
PHP
<?php
|
|
|
|
/**
|
|
* Tableau de bord sync tables statiques — préprod dev, superadm seulement.
|
|
* Source : devinscription_preprod
|
|
*/
|
|
|
|
require_once('inc_start_time.php');
|
|
require_once('inc_fonctions.php');
|
|
require_once('inc_fx_messages.php');
|
|
require_once('inc_fx_static_sync.php');
|
|
|
|
if (!fxAdminStaticSyncAllowed()) {
|
|
header('HTTP/1.1 403 Forbidden');
|
|
echo 'Accès refusé.';
|
|
exit;
|
|
}
|
|
|
|
set_time_limit(300);
|
|
|
|
function ssEsc($str) {
|
|
return htmlspecialchars((string)$str, ENT_QUOTES, 'UTF-8');
|
|
}
|
|
|
|
$strCsrf = fxStaticSyncEnsureCsrfToken();
|
|
$strFlash = '';
|
|
$strFlashType = 'info';
|
|
|
|
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'sync') {
|
|
if (!fxStaticSyncValidateCsrf($_POST['csrf'] ?? '')) {
|
|
$strFlash = 'Jeton CSRF invalide.';
|
|
$strFlashType = 'bad';
|
|
} else {
|
|
$strTable = trim($_POST['table'] ?? '');
|
|
$strEnvId = trim($_POST['env'] ?? '');
|
|
$tabDef = fxStaticSyncFindTableDef($strTable);
|
|
$tabEnv = fxStaticSyncFindEnvDef($strEnvId);
|
|
|
|
if (!$tabDef || !$tabEnv || !empty($tabEnv['is_source'])) {
|
|
$strFlash = 'Paramètres invalides.';
|
|
$strFlashType = 'bad';
|
|
} else {
|
|
$tabConnections = fxStaticSyncOpenConnections();
|
|
$objSource = $tabConnections['dev_preprod'];
|
|
$objTarget = $tabConnections[$strEnvId];
|
|
|
|
if (!$objSource || !$objTarget) {
|
|
$strFlash = 'Connexion BD impossible.';
|
|
$strFlashType = 'bad';
|
|
} else {
|
|
$tabResult = fxStaticSyncCopyTable($objSource, $objTarget, $strTable);
|
|
if ($tabResult['ok']) {
|
|
$strFlash = $tabDef['label'] . ' → ' . $tabEnv['label'] . ' : ' . $tabResult['message'];
|
|
$strFlashType = !empty($tabResult['skipped_cols']) ? 'info' : 'ok';
|
|
} else {
|
|
$strFlash = $tabDef['label'] . ' → ' . $tabEnv['label'] . ' — échec : ' . $tabResult['message'];
|
|
$strFlashType = 'bad';
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
$tabConnections = fxStaticSyncOpenConnections();
|
|
$tabEnvs = fxStaticSyncEnvironmentDefinitions();
|
|
$tabReport = fxStaticSyncBuildReport($tabConnections);
|
|
|
|
$strDetailTable = trim($_GET['detail_table'] ?? '');
|
|
$strDetailEnv = trim($_GET['detail_env'] ?? '');
|
|
$tabDetail = null;
|
|
|
|
if ($strDetailTable !== '' && $strDetailEnv !== '') {
|
|
$tabDetail = fxStaticSyncGetDiffDetail($strDetailTable, $strDetailEnv, $tabConnections);
|
|
}
|
|
|
|
?><!DOCTYPE html>
|
|
<html lang="fr">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<title>Sync tables statiques</title>
|
|
<style>
|
|
body { font-family: Arial, sans-serif; margin: 20px; background: #f4f6f8; color: #222; }
|
|
h1 { font-size: 1.4rem; margin: 0 0 8px; }
|
|
.sub { color: #555; margin-bottom: 20px; font-size: 0.95rem; }
|
|
.flash { padding: 10px 14px; border-radius: 6px; margin-bottom: 16px; white-space: pre-wrap; word-break: break-word; }
|
|
.flash-ok { background: #d4edda; border: 1px solid #b7dfc3; }
|
|
.flash-bad { background: #f8d7da; border: 1px solid #f1b8be; }
|
|
.flash-info { background: #e8f0fe; border: 1px solid #c5d7fb; }
|
|
table { width: 100%; border-collapse: collapse; background: #fff; box-shadow: 0 1px 3px rgba(0,0,0,.08); }
|
|
th, td { border: 1px solid #ddd; padding: 8px 10px; vertical-align: top; font-size: 0.9rem; }
|
|
th { background: #eef2f7; text-align: left; }
|
|
.ss-ok { background: #d4edda; color: #155724; }
|
|
.ss-warn { background: #fff3cd; color: #856404; }
|
|
.ss-bad { background: #f8d7da; color: #721c24; }
|
|
.ss-muted { background: #e9ecef; color: #495057; }
|
|
.badge { display: inline-block; padding: 3px 8px; border-radius: 4px; font-size: 0.82rem; font-weight: 600; }
|
|
.count { font-size: 0.82rem; color: #444; margin-top: 4px; }
|
|
.btn { display: inline-block; margin-top: 6px; padding: 4px 10px; font-size: 0.82rem; border: 1px solid #888; background: #fff; border-radius: 4px; cursor: pointer; text-decoration: none; color: #222; }
|
|
.btn:hover { background: #f0f0f0; }
|
|
.btn-sync { border-color: #2e6da4; color: #1a4f7a; }
|
|
.detail-box { margin-top: 24px; background: #fff; padding: 16px; border: 1px solid #ccc; border-radius: 6px; }
|
|
.detail-box h2 { margin-top: 0; font-size: 1.1rem; }
|
|
.detail-list { margin: 0; padding-left: 18px; }
|
|
.detail-list li { margin-bottom: 8px; }
|
|
.legend { margin: 16px 0; font-size: 0.85rem; }
|
|
.legend span { display: inline-block; margin-right: 12px; padding: 2px 8px; border-radius: 4px; }
|
|
code { background: #f1f1f1; padding: 1px 4px; border-radius: 3px; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
|
|
<h1>Sync tables statiques</h1>
|
|
<p class="sub">
|
|
Source de vérité : <strong>dev préprod</strong> (<code><?php echo ssEsc($arrConDatabasePreProd['db'] ?? ''); ?></code>).
|
|
Comparaison vers dev prod, client préprod et <strong>client prod</strong>.
|
|
Le bouton Sync écrase la table cible avec la source.
|
|
</p>
|
|
|
|
<?php if ($strFlash !== '') { ?>
|
|
<div class="flash flash-<?php echo ssEsc($strFlashType); ?>"><?php echo ssEsc($strFlash); ?></div>
|
|
<?php } ?>
|
|
|
|
<div class="legend">
|
|
<span class="ss-ok">Aligné</span>
|
|
<span class="ss-warn">Manque ou contenu différent</span>
|
|
<span class="ss-bad">Lignes en trop</span>
|
|
<span class="ss-muted">Table absente / connexion</span>
|
|
</div>
|
|
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th>Table</th>
|
|
<th>Source<br><small><?php echo ssEsc($tabEnvs['dev_preprod']['config']['db'] ?? ''); ?></small></th>
|
|
<?php foreach ($tabEnvs as $strEnvId => $tabEnv) {
|
|
if (!empty($tabEnv['is_source'])) {
|
|
continue;
|
|
} ?>
|
|
<th><?php echo ssEsc($tabEnv['label']); ?><br><small><?php echo ssEsc($tabEnv['config']['db'] ?? ''); ?></small></th>
|
|
<?php } ?>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<?php foreach ($tabReport as $tabRow) {
|
|
$tabDef = $tabRow['def']; ?>
|
|
<tr>
|
|
<td>
|
|
<strong><?php echo ssEsc($tabDef['label']); ?></strong><br>
|
|
<code><?php echo ssEsc($tabDef['table']); ?></code>
|
|
</td>
|
|
<td class="ss-muted">
|
|
<span class="badge"><?php echo (int)$tabRow['source_count']; ?> ligne(s)</span>
|
|
</td>
|
|
<?php foreach ($tabEnvs as $strEnvId => $tabEnv) {
|
|
if (!empty($tabEnv['is_source'])) {
|
|
continue;
|
|
}
|
|
$tabCmp = isset($tabRow['envs'][$strEnvId]) ? $tabRow['envs'][$strEnvId] : null;
|
|
if (!$tabCmp) {
|
|
continue;
|
|
}
|
|
$strClass = fxStaticSyncStatusClass($tabCmp['status']);
|
|
$strLabel = fxStaticSyncStatusLabel($tabCmp['status']);
|
|
?>
|
|
<td class="<?php echo ssEsc($strClass); ?>">
|
|
<span class="badge"><?php echo ssEsc($strLabel); ?></span>
|
|
<div class="count">
|
|
<?php echo (int)$tabCmp['target_count']; ?> / <?php echo (int)$tabCmp['source_count']; ?> ligne(s)
|
|
</div>
|
|
<?php if ($tabCmp['status'] !== 'ok' && $tabCmp['status'] !== 'no_connection') { ?>
|
|
<a class="btn" href="?detail_table=<?php echo urlencode($tabDef['table']); ?>&detail_env=<?php echo urlencode($strEnvId); ?>">Détails</a>
|
|
<?php } ?>
|
|
<?php if ($tabCmp['status'] !== 'no_connection') { ?>
|
|
<form method="post" style="display:inline;" onsubmit="return confirm('Écraser <?php echo ssEsc($tabDef['table']); ?> sur <?php echo ssEsc($tabEnv['label']); ?> avec la source dev préprod ?');">
|
|
<input type="hidden" name="action" value="sync">
|
|
<input type="hidden" name="csrf" value="<?php echo ssEsc($strCsrf); ?>">
|
|
<input type="hidden" name="table" value="<?php echo ssEsc($tabDef['table']); ?>">
|
|
<input type="hidden" name="env" value="<?php echo ssEsc($strEnvId); ?>">
|
|
<button type="submit" class="btn btn-sync">Sync →</button>
|
|
</form>
|
|
<?php } ?>
|
|
</td>
|
|
<?php } ?>
|
|
</tr>
|
|
<?php } ?>
|
|
</tbody>
|
|
</table>
|
|
|
|
<?php if (is_array($tabDetail) && !empty($tabDetail['compare'])) {
|
|
$tabCmp = $tabDetail['compare'];
|
|
$tabDef = fxStaticSyncFindTableDef($strDetailTable);
|
|
$tabEnv = fxStaticSyncFindEnvDef($strDetailEnv);
|
|
?>
|
|
<div class="detail-box">
|
|
<h2>Détails — <?php echo ssEsc($tabDef['label'] ?? $strDetailTable); ?> → <?php echo ssEsc($tabEnv['label'] ?? $strDetailEnv); ?></h2>
|
|
<p>
|
|
Manquantes : <?php echo count($tabCmp['missing_keys']); ?> —
|
|
Différences : <?php echo count($tabCmp['diff_keys']); ?> —
|
|
En trop : <?php echo count($tabCmp['extra_keys']); ?>
|
|
<a class="btn" href="sync_static_db.php" style="margin-left:12px;">Fermer</a>
|
|
</p>
|
|
<?php if (!empty($tabDetail['samples'])) { ?>
|
|
<ul class="detail-list">
|
|
<?php foreach ($tabDetail['samples'] as $tabSample) { ?>
|
|
<li>
|
|
<strong><?php echo ssEsc(fxStaticSyncFormatKeyPreview($tabSample['key'])); ?></strong>
|
|
(<?php echo ssEsc($tabSample['type']); ?>)
|
|
<?php if ($tabDef['table'] === 'info' && $tabSample['type'] === 'diff') { ?>
|
|
<br>Source : <em><?php echo ssEsc($tabSample['source_texte']); ?></em>
|
|
<br>Cible : <em><?php echo ssEsc($tabSample['target_texte']); ?></em>
|
|
<?php } ?>
|
|
</li>
|
|
<?php } ?>
|
|
</ul>
|
|
<?php } else { ?>
|
|
<p>Aucun écart à afficher.</p>
|
|
<?php } ?>
|
|
</div>
|
|
<?php } ?>
|
|
|
|
</body>
|
|
</html>
|