This commit introduces the `fxAdminStaticSyncAllowed` function to control access to static database synchronization tools, ensuring only authorized users in development and pre-production environments can access them. The UI is updated to display a new section for these tools in the admin interface, enhancing the overall user experience and access management for development tasks. Additionally, the `.gitignore` file is updated to exclude the `/output/` directory from version control.
221 lines
9.5 KiB
PHP
221 lines
9.5 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;
|
|
}
|
|
|
|
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 = 'ok';
|
|
} else {
|
|
$strFlash = $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; }
|
|
.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 par clé métier + contenu. 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>
|