Add admin console with login, dashboard, content/config/plugin/user management
File-based admin panel accessible at /admin.php with: - Session-based auth with bcrypt hashing and brute-force protection - Dashboard with site statistics and quick actions - Content manager: browse, create, edit, delete files - Config editor with JSON validation - Plugin overview with status indicators - User management: add, remove, change passwords - CSRF protection on all forms, path traversal prevention - Updated README (NL/EN) and guides with admin documentation
This commit is contained in:
19
admin-console/templates/pages/config.php
Normal file
19
admin-console/templates/pages/config.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<h2 class="mb-4"><i class="bi bi-sliders"></i> Configuratie</h2>
|
||||
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-header">
|
||||
<i class="bi bi-filetype-json"></i> config.json
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form method="POST" action="admin.php?route=config">
|
||||
<input type="hidden" name="csrf_token" value="<?= $csrf ?>">
|
||||
<div class="mb-3">
|
||||
<textarea name="config_content" class="form-control font-monospace" rows="25" style="font-size: 0.9rem; tab-size: 4;"><?= htmlspecialchars($siteConfig) ?></textarea>
|
||||
<small class="form-text text-muted">Bewerk de JSON configuratie. Ongeldige JSON wordt niet opgeslagen.</small>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<i class="bi bi-check-lg"></i> Opslaan
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
27
admin-console/templates/pages/content-edit.php
Normal file
27
admin-console/templates/pages/content-edit.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h2><i class="bi bi-pencil"></i> <?= htmlspecialchars($fileName) ?></h2>
|
||||
<a href="admin.php?route=content&dir=<?= urlencode(dirname($_GET['file'] ?? '')) ?>" class="btn btn-outline-secondary btn-sm">
|
||||
<i class="bi bi-arrow-left"></i> Terug
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-body">
|
||||
<form method="POST" action="admin.php?route=content-edit&file=<?= urlencode($_GET['file'] ?? '') ?>">
|
||||
<input type="hidden" name="csrf_token" value="<?= $csrf ?>">
|
||||
<div class="mb-3">
|
||||
<div class="d-flex justify-content-between align-items-center mb-2">
|
||||
<span class="badge bg-secondary"><?= strtoupper($fileExt) ?></span>
|
||||
<small class="text-muted"><?= htmlspecialchars($_GET['file'] ?? '') ?></small>
|
||||
</div>
|
||||
<textarea name="content" class="form-control font-monospace" rows="25" style="font-size: 0.9rem; tab-size: 4;"><?= htmlspecialchars($fileContent) ?></textarea>
|
||||
</div>
|
||||
<div class="d-flex gap-2">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<i class="bi bi-check-lg"></i> Opslaan
|
||||
</button>
|
||||
<a href="admin.php?route=content&dir=<?= urlencode(dirname($_GET['file'] ?? '')) ?>" class="btn btn-outline-secondary">Annuleren</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
46
admin-console/templates/pages/content-new.php
Normal file
46
admin-console/templates/pages/content-new.php
Normal file
@@ -0,0 +1,46 @@
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h2><i class="bi bi-plus-lg"></i> Nieuwe pagina</h2>
|
||||
<a href="admin.php?route=content&dir=<?= urlencode($dir ?? '') ?>" class="btn btn-outline-secondary btn-sm">
|
||||
<i class="bi bi-arrow-left"></i> Terug
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-body">
|
||||
<form method="POST" action="admin.php?route=content-new&dir=<?= urlencode($dir ?? '') ?>">
|
||||
<input type="hidden" name="csrf_token" value="<?= $csrf ?>">
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-8">
|
||||
<label for="filename" class="form-label">Bestandsnaam</label>
|
||||
<input type="text" class="form-control" id="filename" name="filename" placeholder="bijv. mijn-pagina" required>
|
||||
<small class="form-text text-muted">Extensie wordt automatisch toegevoegd.</small>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label for="type" class="form-label">Type</label>
|
||||
<select class="form-select" id="type" name="type">
|
||||
<option value="md" selected>Markdown (.md)</option>
|
||||
<option value="php">PHP (.php)</option>
|
||||
<option value="html">HTML (.html)</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<?php if (!empty($dir)): ?>
|
||||
<div class="mb-3">
|
||||
<small class="text-muted">Map: <?= htmlspecialchars($dir) ?></small>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<div class="mb-3">
|
||||
<label for="content" class="form-label">Inhoud</label>
|
||||
<textarea name="content" id="content" class="form-control font-monospace" rows="20" style="font-size: 0.9rem; tab-size: 4;" placeholder="# Mijn nieuwe pagina
|
||||
|
||||
Schrijf hier je inhoud..."></textarea>
|
||||
</div>
|
||||
<div class="d-flex gap-2">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<i class="bi bi-check-lg"></i> Aanmaken
|
||||
</button>
|
||||
<a href="admin.php?route=content&dir=<?= urlencode($dir ?? '') ?>" class="btn btn-outline-secondary">Annuleren</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
98
admin-console/templates/pages/content.php
Normal file
98
admin-console/templates/pages/content.php
Normal file
@@ -0,0 +1,98 @@
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h2><i class="bi bi-file-earmark-text"></i> Content</h2>
|
||||
<a href="admin.php?route=content-new&dir=<?= urlencode($subdir) ?>" class="btn btn-primary btn-sm">
|
||||
<i class="bi bi-plus-lg"></i> Nieuw bestand
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<?php if (!empty($subdir)): ?>
|
||||
<?php
|
||||
$parentDir = dirname($subdir);
|
||||
$parentLink = $parentDir === '.' ? '' : $parentDir;
|
||||
?>
|
||||
<nav aria-label="breadcrumb" class="mb-3">
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item"><a href="admin.php?route=content"><i class="bi bi-house"></i></a></li>
|
||||
<?php
|
||||
$crumbPath = '';
|
||||
foreach (explode('/', $subdir) as $i => $crumb):
|
||||
$crumbPath .= ($crumbPath ? '/' : '') . $crumb;
|
||||
?>
|
||||
<li class="breadcrumb-item <?= $crumbPath === $subdir ? 'active' : '' ?>">
|
||||
<?php if ($crumbPath === $subdir): ?>
|
||||
<?= htmlspecialchars($crumb) ?>
|
||||
<?php else: ?>
|
||||
<a href="admin.php?route=content&dir=<?= urlencode($crumbPath) ?>"><?= htmlspecialchars($crumb) ?></a>
|
||||
<?php endif; ?>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ol>
|
||||
</nav>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="card shadow-sm">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover mb-0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Naam</th>
|
||||
<th>Type</th>
|
||||
<th>Grootte</th>
|
||||
<th>Gewijzigd</th>
|
||||
<th style="width: 120px;">Acties</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php if (empty($items)): ?>
|
||||
<tr><td colspan="5" class="text-muted text-center py-4">Geen bestanden gevonden.</td></tr>
|
||||
<?php else: ?>
|
||||
<?php foreach ($items as $item): ?>
|
||||
<tr>
|
||||
<td>
|
||||
<?php if ($item['is_dir']): ?>
|
||||
<a href="admin.php?route=content&dir=<?= urlencode($item['path']) ?>">
|
||||
<i class="bi bi-folder-fill text-warning"></i> <?= htmlspecialchars($item['name']) ?>
|
||||
</a>
|
||||
<?php else: ?>
|
||||
<a href="admin.php?route=content-edit&file=<?= urlencode($item['path']) ?>">
|
||||
<?php
|
||||
$icon = match($item['extension']) {
|
||||
'md' => 'bi-file-text text-primary',
|
||||
'php' => 'bi-file-code text-success',
|
||||
'html' => 'bi-file-earmark text-info',
|
||||
default => 'bi-file text-muted'
|
||||
};
|
||||
?>
|
||||
<i class="bi <?= $icon ?>"></i> <?= htmlspecialchars($item['name']) ?>
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td>
|
||||
<?php if ($item['is_dir']): ?>
|
||||
<span class="badge bg-warning text-dark">Map</span>
|
||||
<?php else: ?>
|
||||
<span class="badge bg-secondary"><?= strtoupper($item['extension']) ?></span>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td class="text-muted"><?= $item['size'] ?></td>
|
||||
<td class="text-muted"><?= $item['modified'] ?></td>
|
||||
<td>
|
||||
<?php if (!$item['is_dir']): ?>
|
||||
<a href="admin.php?route=content-edit&file=<?= urlencode($item['path']) ?>" class="btn btn-sm btn-outline-primary" title="Bewerken">
|
||||
<i class="bi bi-pencil"></i>
|
||||
</a>
|
||||
<form method="POST" action="admin.php?route=content-delete&file=<?= urlencode($item['path']) ?>" class="d-inline" onsubmit="return confirm('Weet je zeker dat je dit bestand wilt verwijderen?')">
|
||||
<input type="hidden" name="csrf_token" value="<?= $csrf ?>">
|
||||
<button type="submit" class="btn btn-sm btn-outline-danger" title="Verwijderen">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
78
admin-console/templates/pages/dashboard.php
Normal file
78
admin-console/templates/pages/dashboard.php
Normal file
@@ -0,0 +1,78 @@
|
||||
<h2 class="mb-4"><i class="bi bi-speedometer2"></i> Dashboard</h2>
|
||||
|
||||
<div class="row g-4 mb-4">
|
||||
<div class="col-md-3">
|
||||
<div class="card stat-card shadow-sm">
|
||||
<div class="card-body d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<h6 class="text-muted mb-1">Pagina's</h6>
|
||||
<h3 class="mb-0"><?= $stats['pages'] ?></h3>
|
||||
</div>
|
||||
<i class="bi bi-file-earmark-text stat-icon text-primary"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="card stat-card shadow-sm">
|
||||
<div class="card-body d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<h6 class="text-muted mb-1">Mappen</h6>
|
||||
<h3 class="mb-0"><?= $stats['directories'] ?></h3>
|
||||
</div>
|
||||
<i class="bi bi-folder stat-icon text-warning"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="card stat-card shadow-sm">
|
||||
<div class="card-body d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<h6 class="text-muted mb-1">Plugins</h6>
|
||||
<h3 class="mb-0"><?= $stats['plugins'] ?></h3>
|
||||
</div>
|
||||
<i class="bi bi-plug stat-icon text-success"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="card stat-card shadow-sm">
|
||||
<div class="card-body d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<h6 class="text-muted mb-1">Content grootte</h6>
|
||||
<h3 class="mb-0"><?= $stats['content_size'] ?></h3>
|
||||
</div>
|
||||
<i class="bi bi-hdd stat-icon text-info"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-4">
|
||||
<div class="col-md-6">
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-header"><i class="bi bi-info-circle"></i> Site informatie</div>
|
||||
<div class="card-body">
|
||||
<table class="table table-sm mb-0">
|
||||
<tr><td class="text-muted">Site titel</td><td><?= htmlspecialchars($siteConfig['site_title'] ?? 'CodePress') ?></td></tr>
|
||||
<tr><td class="text-muted">Standaard taal</td><td><?= htmlspecialchars($siteConfig['language']['default'] ?? 'nl') ?></td></tr>
|
||||
<tr><td class="text-muted">Auteur</td><td><?= htmlspecialchars($siteConfig['author']['name'] ?? '-') ?></td></tr>
|
||||
<tr><td class="text-muted">PHP versie</td><td><?= $stats['php_version'] ?></td></tr>
|
||||
<tr><td class="text-muted">Config geladen</td><td><?= $stats['config_exists'] ? '<span class="badge bg-success">Ja</span>' : '<span class="badge bg-danger">Nee</span>' ?></td></tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-header"><i class="bi bi-lightning"></i> Snelle acties</div>
|
||||
<div class="card-body">
|
||||
<div class="d-grid gap-2">
|
||||
<a href="admin.php?route=content-new" class="btn btn-outline-primary"><i class="bi bi-plus-lg"></i> Nieuwe pagina</a>
|
||||
<a href="admin.php?route=config" class="btn btn-outline-secondary"><i class="bi bi-sliders"></i> Configuratie bewerken</a>
|
||||
<a href="admin.php?route=content" class="btn btn-outline-info"><i class="bi bi-folder2-open"></i> Content beheren</a>
|
||||
<a href="index.php" target="_blank" class="btn btn-outline-success"><i class="bi bi-box-arrow-up-right"></i> Website bekijken</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
44
admin-console/templates/pages/plugins.php
Normal file
44
admin-console/templates/pages/plugins.php
Normal file
@@ -0,0 +1,44 @@
|
||||
<h2 class="mb-4"><i class="bi bi-plug"></i> Plugins</h2>
|
||||
|
||||
<?php if (empty($plugins)): ?>
|
||||
<div class="alert alert-info">Geen plugins gevonden in de plugins map.</div>
|
||||
<?php else: ?>
|
||||
<div class="row g-4">
|
||||
<?php foreach ($plugins as $plugin): ?>
|
||||
<div class="col-md-6">
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<strong><i class="bi bi-plug"></i> <?= htmlspecialchars($plugin['name']) ?></strong>
|
||||
<?php if ($plugin['enabled']): ?>
|
||||
<span class="badge bg-success">Actief</span>
|
||||
<?php else: ?>
|
||||
<span class="badge bg-secondary">Inactief</span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<table class="table table-sm mb-0">
|
||||
<tr>
|
||||
<td class="text-muted">Hoofdbestand</td>
|
||||
<td>
|
||||
<?= $plugin['has_main'] ? '<i class="bi bi-check-circle text-success"></i> Aanwezig' : '<i class="bi bi-x-circle text-danger"></i> Ontbreekt' ?>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="text-muted">Configuratie</td>
|
||||
<td>
|
||||
<?= $plugin['has_config'] ? '<i class="bi bi-check-circle text-success"></i> Aanwezig' : '<i class="bi bi-dash-circle text-muted"></i> Geen' ?>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="text-muted">README</td>
|
||||
<td>
|
||||
<?= $plugin['has_readme'] ? '<i class="bi bi-check-circle text-success"></i> Aanwezig' : '<i class="bi bi-dash-circle text-muted"></i> Geen' ?>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
93
admin-console/templates/pages/users.php
Normal file
93
admin-console/templates/pages/users.php
Normal file
@@ -0,0 +1,93 @@
|
||||
<h2 class="mb-4"><i class="bi bi-people"></i> Gebruikers</h2>
|
||||
|
||||
<div class="row g-4">
|
||||
<!-- Users list -->
|
||||
<div class="col-md-7">
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-header"><i class="bi bi-list"></i> Huidige gebruikers</div>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover mb-0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Gebruikersnaam</th>
|
||||
<th>Rol</th>
|
||||
<th>Aangemaakt</th>
|
||||
<th style="width: 160px;">Acties</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($users as $u): ?>
|
||||
<tr>
|
||||
<td>
|
||||
<i class="bi bi-person-circle"></i>
|
||||
<?= htmlspecialchars($u['username']) ?>
|
||||
<?php if ($u['username'] === $user['username']): ?>
|
||||
<span class="badge bg-info">Jij</span>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td><span class="badge bg-primary"><?= htmlspecialchars($u['role']) ?></span></td>
|
||||
<td class="text-muted"><?= htmlspecialchars($u['created']) ?></td>
|
||||
<td>
|
||||
<!-- Change password -->
|
||||
<form method="POST" action="admin.php?route=users" class="d-inline">
|
||||
<input type="hidden" name="csrf_token" value="<?= $csrf ?>">
|
||||
<input type="hidden" name="action" value="change_password">
|
||||
<input type="hidden" name="pw_username" value="<?= htmlspecialchars($u['username']) ?>">
|
||||
<div class="input-group input-group-sm d-inline-flex" style="width: auto;">
|
||||
<input type="password" name="new_password" placeholder="Nieuw ww" class="form-control form-control-sm" style="width: 100px;" required minlength="8">
|
||||
<button type="submit" class="btn btn-sm btn-outline-warning" title="Wachtwoord wijzigen">
|
||||
<i class="bi bi-key"></i>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<?php if ($u['username'] !== $user['username']): ?>
|
||||
<form method="POST" action="admin.php?route=users" class="d-inline ms-1" onsubmit="return confirm('Weet je zeker dat je deze gebruiker wilt verwijderen?')">
|
||||
<input type="hidden" name="csrf_token" value="<?= $csrf ?>">
|
||||
<input type="hidden" name="action" value="delete">
|
||||
<input type="hidden" name="delete_username" value="<?= htmlspecialchars($u['username']) ?>">
|
||||
<button type="submit" class="btn btn-sm btn-outline-danger" title="Verwijderen">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Add user form -->
|
||||
<div class="col-md-5">
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-header"><i class="bi bi-person-plus"></i> Gebruiker toevoegen</div>
|
||||
<div class="card-body">
|
||||
<form method="POST" action="admin.php?route=users">
|
||||
<input type="hidden" name="csrf_token" value="<?= $csrf ?>">
|
||||
<input type="hidden" name="action" value="add">
|
||||
<div class="mb-3">
|
||||
<label for="username" class="form-label">Gebruikersnaam</label>
|
||||
<input type="text" class="form-control" id="username" name="username" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="password" class="form-label">Wachtwoord</label>
|
||||
<input type="password" class="form-control" id="password" name="password" required minlength="8">
|
||||
<small class="form-text text-muted">Minimaal 8 tekens.</small>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="role" class="form-label">Rol</label>
|
||||
<select class="form-select" id="role" name="role">
|
||||
<option value="admin">Admin</option>
|
||||
<option value="editor">Editor</option>
|
||||
</select>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<i class="bi bi-person-plus"></i> Toevoegen
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
Reference in New Issue
Block a user