Admin tools cleanup and view SQL modal
parent
eebb815ad1
commit
de486ba7e7
|
@ -30,6 +30,48 @@ if (!$canAdmin) {
|
||||||
|
|
||||||
// Handle actions
|
// Handle actions
|
||||||
$action = $_POST['action'] ?? '';
|
$action = $_POST['action'] ?? '';
|
||||||
|
|
||||||
|
// AJAX: view migration file contents
|
||||||
|
if ($action === 'read_migration') {
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
// CSRF check
|
||||||
|
$csrfHeader = $_SERVER['HTTP_X_CSRF_TOKEN'] ?? '';
|
||||||
|
$csrfToken = $_POST['csrf_token'] ?? $csrfHeader;
|
||||||
|
if (!$security->verifyCsrfToken($csrfToken)) {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Invalid CSRF token']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Permission check
|
||||||
|
if (!$canAdmin) {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Permission denied']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate filename to avoid traversal
|
||||||
|
$filename = basename($_POST['filename'] ?? '');
|
||||||
|
if ($filename === '' || !preg_match('/^[A-Za-z0-9_\-]+\.sql$/', $filename)) {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Invalid filename']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$migrationsDir = __DIR__ . '/../../doc/database/migrations';
|
||||||
|
$path = realpath($migrationsDir . '/' . $filename);
|
||||||
|
if ($path === false || strpos($path, realpath($migrationsDir)) !== 0) {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'File not found']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$content = @file_get_contents($path);
|
||||||
|
if ($content === false) {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Could not read file']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode(['success' => true, 'name' => $filename, 'content' => $content]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
if ($action !== '') {
|
if ($action !== '') {
|
||||||
if (!$security->verifyCsrfToken($_POST['csrf_token'] ?? '')) {
|
if (!$security->verifyCsrfToken($_POST['csrf_token'] ?? '')) {
|
||||||
Feedback::flash('SECURITY', 'CSRF_INVALID');
|
Feedback::flash('SECURITY', 'CSRF_INVALID');
|
||||||
|
@ -75,10 +117,22 @@ require_once __DIR__ . '/../core/MigrationRunner.php';
|
||||||
$migrationsDir = __DIR__ . '/../../doc/database/migrations';
|
$migrationsDir = __DIR__ . '/../../doc/database/migrations';
|
||||||
$pending = [];
|
$pending = [];
|
||||||
$applied = [];
|
$applied = [];
|
||||||
|
$migration_contents = [];
|
||||||
try {
|
try {
|
||||||
$runner = new \App\Core\MigrationRunner($db, $migrationsDir);
|
$runner = new \App\Core\MigrationRunner($db, $migrationsDir);
|
||||||
$pending = $runner->listPendingMigrations();
|
$pending = $runner->listPendingMigrations();
|
||||||
$applied = $runner->listAppliedMigrations();
|
$applied = $runner->listAppliedMigrations();
|
||||||
|
// Preload contents for billing-admin style modals
|
||||||
|
$all = array_unique(array_merge($pending, $applied));
|
||||||
|
foreach ($all as $fname) {
|
||||||
|
$path = realpath($migrationsDir . '/' . $fname);
|
||||||
|
if ($path && strpos($path, realpath($migrationsDir)) === 0) {
|
||||||
|
$content = @file_get_contents($path);
|
||||||
|
if ($content !== false) {
|
||||||
|
$migration_contents[$fname] = $content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
} catch (Throwable $e) {
|
} catch (Throwable $e) {
|
||||||
// show error in the page
|
// show error in the page
|
||||||
$migration_error = $e->getMessage();
|
$migration_error = $e->getMessage();
|
||||||
|
|
|
@ -60,11 +60,22 @@
|
||||||
<div><strong>Pending</strong></div>
|
<div><strong>Pending</strong></div>
|
||||||
<span class="badge <?= empty($pending) ? 'bg-success' : 'bg-warning text-dark' ?>"><?= count($pending) ?></span>
|
<span class="badge <?= empty($pending) ? 'bg-success' : 'bg-warning text-dark' ?>"><?= count($pending) ?></span>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-2 small">
|
<div class="mt-2 small border rounded" style="max-height: 240px; overflow: auto;">
|
||||||
<?php if (empty($pending)): ?>
|
<?php if (empty($pending)): ?>
|
||||||
<span class="text-success">none</span>
|
<div class="p-2"><span class="text-success">none</span></div>
|
||||||
<?php else: ?>
|
<?php else: ?>
|
||||||
<code><?= htmlspecialchars(implode(', ', $pending)) ?></code>
|
<ul class="list-group list-group-flush">
|
||||||
|
<?php foreach ($pending as $fname): ?>
|
||||||
|
<li class="list-group-item d-flex justify-content-between align-items-center">
|
||||||
|
<span class="text-monospace small"><?= htmlspecialchars($fname) ?></span>
|
||||||
|
<button type="button"
|
||||||
|
class="btn btn-outline-primary btn-sm"
|
||||||
|
data-toggle="modal"
|
||||||
|
data-target="#migrationModal<?= md5($fname) ?>">View
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</ul>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -73,11 +84,22 @@
|
||||||
<div><strong>Applied</strong></div>
|
<div><strong>Applied</strong></div>
|
||||||
<span class="badge bg-secondary"><?= count($applied) ?></span>
|
<span class="badge bg-secondary"><?= count($applied) ?></span>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-2 small">
|
<div class="mt-2 small border rounded" style="max-height: 240px; overflow: auto;">
|
||||||
<?php if (empty($applied)): ?>
|
<?php if (empty($applied)): ?>
|
||||||
<span class="text-muted">none</span>
|
<div class="p-2"><span class="text-muted">none</span></div>
|
||||||
<?php else: ?>
|
<?php else: ?>
|
||||||
<code><?= htmlspecialchars(implode(', ', $applied)) ?></code>
|
<ul class="list-group list-group-flush">
|
||||||
|
<?php foreach ($applied as $fname): ?>
|
||||||
|
<li class="list-group-item d-flex justify-content-between align-items-center">
|
||||||
|
<span class="text-monospace small"><?= htmlspecialchars($fname) ?></span>
|
||||||
|
<button type="button"
|
||||||
|
class="btn btn-outline-secondary btn-sm"
|
||||||
|
data-toggle="modal"
|
||||||
|
data-target="#migrationModal<?= md5($fname) ?>">View
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</ul>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -91,3 +113,27 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Migration viewer modals (one per file) -->
|
||||||
|
<?php if (!empty($migration_contents)):
|
||||||
|
foreach ($migration_contents as $name => $content):
|
||||||
|
$modalId = 'migrationModal' . md5($name);
|
||||||
|
?>
|
||||||
|
<div class="modal fade" id="<?= $modalId ?>" tabindex="-1" aria-labelledby="<?= $modalId ?>Label" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-lg">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" id="<?= $modalId ?>Label"><?= htmlspecialchars($name) ?></h5>
|
||||||
|
<button type="button" class="btn-close" data-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body p-0">
|
||||||
|
<pre class="mb-0" style="max-height: 60vh; overflow: auto;"><code class="p-3 d-block"><?= htmlspecialchars($content) ?></code></pre>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php endforeach;
|
||||||
|
endif; ?>
|
||||||
|
|
Loading…
Reference in New Issue