Adds simple admin-tools page

main
Yasen Pramatarov 2025-09-25 11:37:54 +03:00
parent f22fa76987
commit a77cf5b328
4 changed files with 171 additions and 2 deletions

View File

@ -0,0 +1,94 @@
<?php
/**
* Admin tools controller
*
* Allows superusers to:
* - Enable/disable maintenance mode
* - Run database migrations
*/
// Security and CSRF
require_once __DIR__ . '/../helpers/security.php';
$security = SecurityHelper::getInstance();
// Must be logged in
if (!Session::isValidSession()) {
header('Location: ' . $app_root . '?page=login');
exit;
}
// Must be superuser
$canAdmin = false;
if (isset($userId) && isset($userObject) && method_exists($userObject, 'hasRight')) {
$canAdmin = ($userId === 1) || (bool)$userObject->hasRight($userId, 'superuser');
}
if (!$canAdmin) {
Feedback::flash('SECURITY', 'PERMISSION_DENIED');
header('Location: ' . $app_root);
exit;
}
// Handle actions
$action = $_POST['action'] ?? '';
if ($action !== '') {
if (!$security->verifyCsrfToken($_POST['csrf_token'] ?? '')) {
Feedback::flash('SECURITY', 'CSRF_INVALID');
header('Location: ' . $app_root . '?page=admin-tools');
exit;
}
try {
if ($action === 'maintenance_on') {
require_once __DIR__ . '/../core/Maintenance.php';
$msg = trim($_POST['maintenance_message'] ?? '');
\App\Core\Maintenance::enable($msg);
Feedback::flash('NOTICE', 'DEFAULT', 'Maintenance mode enabled.', true);
} elseif ($action === 'maintenance_off') {
require_once __DIR__ . '/../core/Maintenance.php';
\App\Core\Maintenance::disable();
Feedback::flash('NOTICE', 'DEFAULT', 'Maintenance mode disabled.', true);
} elseif ($action === 'migrate_up') {
require_once __DIR__ . '/../core/MigrationRunner.php';
$migrationsDir = __DIR__ . '/../../doc/database/migrations';
$runner = new \App\Core\MigrationRunner($db, $migrationsDir);
$applied = $runner->applyPendingMigrations();
if (empty($applied)) {
Feedback::flash('NOTICE', 'DEFAULT', 'No pending migrations.', true);
} else {
Feedback::flash('NOTICE', 'DEFAULT', 'Applied migrations: ' . implode(', ', $applied), true);
}
}
} catch (Throwable $e) {
Feedback::flash('ERROR', 'DEFAULT', 'Action failed: ' . $e->getMessage(), false);
}
header('Location: ' . $app_root . '?page=admin-tools');
exit;
}
// Prepare data for view
require_once __DIR__ . '/../core/Maintenance.php';
$maintenance_enabled = \App\Core\Maintenance::isEnabled();
$maintenance_message = \App\Core\Maintenance::getMessage();
require_once __DIR__ . '/../core/MigrationRunner.php';
$migrationsDir = __DIR__ . '/../../doc/database/migrations';
$pending = [];
$applied = [];
try {
$runner = new \App\Core\MigrationRunner($db, $migrationsDir);
$pending = $runner->listPendingMigrations();
$applied = $runner->listAppliedMigrations();
} catch (Throwable $e) {
// show error in the page
$migration_error = $e->getMessage();
}
// CSRF token
$csrf_token = $security->generateCsrfToken();
// Get any new feedback messages
include __DIR__ . '/../helpers/feedback.php';
// Load the template
include __DIR__ . '/../templates/admin-tools.php';

View File

@ -0,0 +1,69 @@
<?php
/** @var bool $maintenance_enabled */
/** @var string $maintenance_message */
/** @var array $pending */
/** @var array $applied */
/** @var string $csrf_token */
?>
<div class="container mt-4">
<h2>Admin tools</h2>
<p class="text-muted">System maintenance and database utilities.</p>
<div class="row mt-4">
<div class="col-md-6 mb-4">
<div class="card">
<div class="card-header">Maintenance mode</div>
<div class="card-body">
<p>Status: <strong class="<?= $maintenance_enabled ? 'text-danger' : 'text-success' ?>">
<?= $maintenance_enabled ? 'Enabled' : 'Disabled' ?></strong></p>
<form method="post" class="mb-2">
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($csrf_token) ?>">
<input type="hidden" name="action" value="maintenance_on">
<div class="mb-2">
<label for="maintenance_message" class="form-label">Message (optional)</label>
<input type="text" id="maintenance_message" name="maintenance_message" class="form-control" value="<?= htmlspecialchars($maintenance_message) ?>" placeholder="Upgrading database">
</div>
<button type="submit" class="btn btn-warning" <?= $maintenance_enabled ? 'disabled' : '' ?>>Enable maintenance</button>
</form>
<form method="post">
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($csrf_token) ?>">
<input type="hidden" name="action" value="maintenance_off">
<button type="submit" class="btn btn-secondary" <?= $maintenance_enabled ? '' : 'disabled' ?>>Disable maintenance</button>
</form>
</div>
</div>
</div>
<div class="col-md-6 mb-4">
<div class="card">
<div class="card-header">Database migrations</div>
<div class="card-body">
<?php if (!empty($migration_error)): ?>
<div class="alert alert-danger">Error: <?= htmlspecialchars($migration_error) ?></div>
<?php endif; ?>
<p>
<strong>Pending (<?= count($pending) ?>):</strong>
<?php if (empty($pending)): ?>
<span class="text-success">None</span>
<?php else: ?>
<code><?= htmlspecialchars(implode(', ', $pending)) ?></code>
<?php endif; ?>
</p>
<p>
<strong>Applied (<?= count($applied) ?>):</strong>
<?php if (empty($applied)): ?>
<span class="text-muted">None</span>
<?php else: ?>
<code><?= htmlspecialchars(implode(', ', $applied)) ?></code>
<?php endif; ?>
</p>
<form method="post">
<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" <?= empty($pending) ? 'disabled' : '' ?>>Apply pending migrations</button>
</form>
</div>
</div>
</div>
</div>
</div>

View File

@ -69,8 +69,13 @@
</a>
<div class="dropdown-menu dropdown-menu-right">
<h6 class="dropdown-header">system</h6>
<?php if ($userObject->hasRight($userId, 'superuser') ||
$userObject->hasRight($userId, 'view config file')) {?>
<?php if ($userObject->hasRight($userId, 'superuser')) {?>
<a class="dropdown-item" href="<?= htmlspecialchars($app_root) ?>?page=admin-tools">
<i class="fas fa-toolbox"></i>Admin tools
</a>
<?php } ?>
<?php if ($userObject->hasRight($userId, 'superuser') ||
$userObject->hasRight($userId, 'view config file')) {?>
<a class="dropdown-item" href="<?= htmlspecialchars($app_root) ?>?page=config">
<i class="fas fa-wrench"></i>Configuration
</a>

View File

@ -146,6 +146,7 @@ $allowed_urls = [
'graphs','latest','livejs','agents',
'profile','credentials','config','security',
'settings','theme','theme-asset',
'admin-tools',
'status',
'help','about',
'login','logout',