Redesigns admin-tools page

main
Yasen Pramatarov 2025-11-23 22:48:54 +02:00
parent 35def007ca
commit 8eae3cf124
1 changed files with 155 additions and 129 deletions

View File

@ -6,142 +6,151 @@
/** @var string $csrf_token */ /** @var string $csrf_token */
?> ?>
<!-- admin tools page --> <!-- admin tools page -->
<div class="container-fluid mt-2"> <section class="tm-hero">
<div class="row mb-4"> <div class="tm-hero-card">
<div class="col-12 mb-2"> <div class="tm-hero-body">
<h2>Admin tools</h2> <div class="tm-hero-heading">
<small class="text-muted">System maintenance and database utilities.</small> <h1 class="tm-hero-title">Admin tools</h1>
</div> <p class="tm-hero-subtitle">Centralized maintenance and database utilities to keep <?= htmlspecialchars($config['site_name']) ?> healthy.</p>
</div> </div>
<div class="tm-hero-meta">
<div class="row g-4"> <span class="tm-hero-pill <?= $maintenance_enabled ? 'pill-danger' : 'pill-success' ?>">
<div class="col-lg-6"> <i class="fas fa-power-off"></i>
<div class="card shadow-sm"> Maintenance <?= $maintenance_enabled ? 'enabled' : 'not enabled' ?>
<div class="card-header bg-light d-flex justify-content-between align-items-center py-3"> </span>
<h5 class="card-title mb-0"> <span class="tm-hero-pill <?= empty($pending) ? 'pill-neutral' : 'pill-danger' ?>">
<i class="fas fa-tools me-2 text-secondary"></i> <i class="fas fa-database"></i>
Maintenance mode <?= count($pending) ?> pending migration<?= count($pending) === 1 ? '' : 's' ?>
</h5> </span>
<span class="badge <?= $maintenance_enabled ? 'bg-danger' : 'bg-success' ?>"><?= $maintenance_enabled ? 'enabled' : 'disabled' ?></span>
</div>
<div class="card-body p-4">
<form method="post" class="mb-3">
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($csrf_token) ?>">
<input type="hidden" name="action" value="maintenance_on">
<div class="mb-3">
<label for="maintenance_message" class="form-label mb-1">Message (optional)</label>
<input type="text" id="maintenance_message" name="maintenance_message" class="form-control form-control-sm" value="<?= htmlspecialchars($maintenance_message) ?>" placeholder="Upgrading database">
</div>
<button type="submit" class="btn btn-warning btn-sm" <?= $maintenance_enabled ? 'disabled' : '' ?>>Enable maintenance</button>
</form>
<form method="post" class="mt-2">
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($csrf_token) ?>">
<input type="hidden" name="action" value="maintenance_off">
<button type="submit" class="btn btn-outline-secondary btn-sm" <?= $maintenance_enabled ? '' : 'disabled' ?>>Disable maintenance</button>
</form>
</div>
</div> </div>
</div> </div>
<a class="btn btn-primary tm-directory-cta" href="<?= htmlspecialchars($app_root) ?>?page=dashboard">
<i class="fas fa-arrow-left"></i>
Back to dashboard
</a>
</div>
</section>
<div class="col-lg-6"> <section class="tm-admin">
<div class="card shadow-sm">
<div class="card-header bg-light d-flex justify-content-between align-items-center py-3"> <div class="tm-admin-grid">
<h5 class="card-title mb-0"> <article class="tm-admin-card">
<i class="fas fa-database me-2 text-secondary"></i> <header>
Database migrations <div>
</h5> <h2 class="tm-admin-card-title">Maintenance mode</h2>
<p class="tm-admin-card-subtitle">Let your team know when maintenance is in progress.</p>
</div> </div>
<div class="card-body p-4"> <span class="tm-hero-pill <?= $maintenance_enabled ? 'pill-danger' : 'pill-neutral' ?>"><?= $maintenance_enabled ? 'enabled' : 'disabled' ?></span>
<?php if (!empty($migration_error)): ?> </header>
<div class="alert alert-danger">Error: <?= htmlspecialchars($migration_error) ?></div>
<?php endif; ?> <div class="tm-admin-section">
<div class="alert alert-info mb-3"> <p class="tm-admin-section-title">Message</p>
<strong>Test Migration Tools</strong><br> <form method="post" class="tm-admin-controls">
<small class="text-muted">These tools create fake migrations in the database only (no files) for testing the migration warning functionality.</small> <input type="hidden" name="csrf_token" value="<?= htmlspecialchars($csrf_token) ?>">
<input type="hidden" name="action" value="maintenance_on">
<input type="text" id="maintenance_message" name="maintenance_message" class="tm-admin-message-input" value="<?= htmlspecialchars($maintenance_message) ?>" placeholder="Upgrading database">
<div class="tm-admin-inline-actions">
<button type="submit" class="btn btn-warning" <?= $maintenance_enabled ? 'disabled' : '' ?>>Enable maintenance</button>
<button type="button" class="btn btn-outline-secondary" <?= $maintenance_enabled ? '' : 'disabled' ?> onclick="document.getElementById('maintenance-disable-form').submit();">Disable maintenance</button>
</div> </div>
<div class="d-flex gap-2 mb-3"> </form>
<form method="post" class="d-inline"> <form method="post" id="maintenance-disable-form" class="d-none">
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($csrf_token) ?>"> <input type="hidden" name="csrf_token" value="<?= htmlspecialchars($csrf_token) ?>">
<input type="hidden" name="action" value="create_test_migration"> <input type="hidden" name="action" value="maintenance_off">
<button type="submit" class="btn btn-outline-info btn-sm" <?= !empty($test_migrations_exist) ? 'disabled' : '' ?>>Create test migration</button> </form>
</form> </div>
<form method="post" class="d-inline"> </article>
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($csrf_token) ?>">
<input type="hidden" name="action" value="clear_test_migrations"> <article class="tm-admin-card tm-admin-card--migrations">
<button type="submit" class="btn btn-outline-secondary btn-sm" <?= empty($test_migrations_exist) ? 'disabled' : '' ?>>Clear test migrations</button> <header>
</form> <div>
</div> <h2 class="tm-admin-card-title">Database migrations</h2>
<div class="mb-3"> <p class="tm-admin-card-subtitle">Review pending SQL and apply with confidence.</p>
<div class="d-flex justify-content-between align-items-center"> </div>
<div> </header>
<strong>Pending</strong>
<?php if (!empty($next_pending)): ?> <?php if (!empty($migration_error)): ?>
<span class="badge bg-info text-dark ms-2">Next: <?= htmlspecialchars($next_pending) ?></span> <div class="alert alert-danger">Error: <?= htmlspecialchars($migration_error) ?></div>
<?php endif; ?> <?php endif; ?>
</div>
<span class="badge <?= empty($pending) ? 'bg-success' : 'bg-warning text-dark' ?>"><?= count($pending) ?></span> <div class="tm-admin-test-tools">
</div> <p><strong>Test migration tools</strong></p>
<div class="mt-2 small border rounded" style="max-height: 240px; overflow: auto;"> <div class="tm-admin-inline-actions">
<?php if (empty($pending)): ?> <form method="post">
<div class="p-2"><span class="text-success">none</span></div> <input type="hidden" name="csrf_token" value="<?= htmlspecialchars($csrf_token) ?>">
<?php else: ?> <input type="hidden" name="action" value="create_test_migration">
<ul class="list-group list-group-flush"> <button type="submit" class="btn btn-outline-primary btn-sm" <?= !empty($test_migrations_exist) ? 'disabled' : '' ?>>Create test migration</button>
<?php foreach ($pending as $fname): ?> </form>
<li class="list-group-item d-flex justify-content-between align-items-center"> <form method="post">
<span class="text-monospace small"><?= htmlspecialchars($fname) ?></span> <input type="hidden" name="csrf_token" value="<?= htmlspecialchars($csrf_token) ?>">
<div class="d-flex gap-2"> <input type="hidden" name="action" value="clear_test_migrations">
<button type="button" <button type="submit" class="btn btn-outline-secondary btn-sm" <?= empty($test_migrations_exist) ? 'disabled' : '' ?>>Clear test migrations</button>
class="btn btn-outline-primary btn-sm" </form>
data-toggle="modal"
data-target="#migrationModal<?= md5($fname) ?>">View
</button>
</div>
</li>
<?php endforeach; ?>
</ul>
<?php endif; ?>
</div>
</div>
<div class="mb-3">
<div class="d-flex justify-content-between align-items-center">
<div><strong>Applied</strong></div>
<span class="badge bg-secondary"><?= count($applied) ?></span>
</div>
<div class="mt-2 small border rounded" style="max-height: 240px; overflow: auto;">
<?php if (empty($applied)): ?>
<div class="p-2"><span class="text-muted">none</span></div>
<?php else: ?>
<ul class="list-group list-group-flush">
<?php foreach ($applied as $fname):
if (strpos($fname, '_test_migration') !== false) {
continue;
}
?>
<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; ?>
</div>
</div>
<div class="d-flex gap-2 mt-3">
<form method="post" class="tm-confirm" data-confirm="Apply all pending migrations?">
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($csrf_token) ?>">
<input type="hidden" name="action" value="migrate_up">
<button type="submit" class="btn btn-primary btn-sm" <?= empty($pending) ? 'disabled' : '' ?>>Apply all pending</button>
</form>
</div>
</div> </div>
</div> </div>
</div>
<div class="tm-admin-section">
<div class="d-flex justify-content-between align-items-center">
<p class="tm-admin-section-title mb-0">Pending migrations</p>
<?php if (!empty($next_pending)): ?>
<span class="badge bg-info text-dark">Next: <?= htmlspecialchars($next_pending) ?></span>
<?php endif; ?>
</div>
<ul class="tm-admin-list">
<?php if (empty($pending)): ?>
<li class="tm-admin-empty">No pending migrations</li>
<?php else: ?>
<?php foreach ($pending as $fname): ?>
<li>
<div class="tm-admin-list-actions">
<button type="button" class="btn btn-sm btn-outline-primary"
data-toggle="modal" data-target="#migrationModal<?= md5($fname) ?>">
View
</button>
</div>
<span><?= htmlspecialchars($fname) ?></span>
</li>
<?php endforeach; ?>
<?php endif; ?>
</ul>
</div>
<div class="tm-admin-section">
<div class="d-flex justify-content-between align-items-center">
<p class="tm-admin-section-title mb-0">Applied migrations</p>
<span class="badge bg-secondary"><?= count($applied) ?></span>
</div>
<ul class="tm-admin-list">
<?php if (empty($applied)): ?>
<li class="tm-admin-empty">No applied migrations yet</li>
<?php else: ?>
<?php foreach ($applied as $fname):
if (strpos($fname, '_test_migration') !== false) {
continue;
}
?>
<li>
<div class="tm-admin-list-actions">
<button type="button" class="btn btn-sm btn-outline-secondary"
data-toggle="modal" data-target="#migrationModal<?= md5($fname) ?>">
View
</button>
</div>
<span><?= htmlspecialchars($fname) ?></span>
</li>
<?php endforeach; ?>
<?php endif; ?>
</ul>
</div>
<form method="post" class="tm-confirm" data-confirm="Apply all pending migrations?">
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($csrf_token) ?>">
<input type="hidden" name="action" value="migrate_up">
<button type="submit" class="btn btn-danger w-100" <?= empty($pending) ? 'disabled' : '' ?>>Apply all pending</button>
</form>
</article>
</div> </div>
</div> </section>
<!-- Migration viewer modals (one per file) --> <!-- Migration viewer modals (one per file) -->
<?php if (!empty($migration_contents)): <?php if (!empty($migration_contents)):
@ -155,10 +164,19 @@
<h5 class="modal-title" id="<?= $modalId ?>Label"><?= htmlspecialchars($name) ?></h5> <h5 class="modal-title" id="<?= $modalId ?>Label"><?= htmlspecialchars($name) ?></h5>
<button type="button" class="btn-close" data-dismiss="modal" aria-label="Close"></button> <button type="button" class="btn-close" data-dismiss="modal" aria-label="Close"></button>
</div> </div>
<?php
$record = $migration_records[$name] ?? null;
$appliedAtRaw = $record['applied_at'] ?? null;
$appliedAtFormatted = null;
if (!empty($appliedAtRaw)) {
$timestamp = strtotime($appliedAtRaw);
$appliedAtFormatted = $timestamp ? date('M d, Y H:i', $timestamp) : $appliedAtRaw;
}
?>
<div class="modal-body p-0"> <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> <pre class="tm-admin-modal-code"><code style="border-radius: 0.5rem;"><?= htmlspecialchars($content) ?></code></pre>
</div> </div>
<?php <?php
$isModalNext = (!empty($next_pending) && $next_pending === $name); $isModalNext = (!empty($next_pending) && $next_pending === $name);
$modalResult = (!empty($migration_modal_result) && ($migration_modal_result['name'] ?? '') === $name) ? $migration_modal_result : null; $modalResult = (!empty($migration_modal_result) && ($migration_modal_result['name'] ?? '') === $name) ? $migration_modal_result : null;
?> ?>
@ -175,6 +193,14 @@
<div class="alert alert-<?= $modalResult['status'] === 'success' ? 'success' : 'info' ?> mb-0 small"> <div class="alert alert-<?= $modalResult['status'] === 'success' ? 'success' : 'info' ?> mb-0 small">
<?= htmlspecialchars($modalResult['message']) ?> <?= htmlspecialchars($modalResult['message']) ?>
</div> </div>
<?php endif; ?>
<?php if ($appliedAtFormatted): ?>
<div class="tm-admin-modal-meta">
<span class="tm-admin-pill pill-success">
<i class="far fa-clock"></i>
Applied <?= htmlspecialchars($appliedAtFormatted) ?>
</span>
</div>
<?php endif; ?> <?php endif; ?>
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button> <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
</div> </div>