Security: verwijder hardcoded wachtwoord, voeg random-wachtwoord-generator toe bij eerste installatie

This commit is contained in:
2026-08-27 08:57:05 +00:00
parent 6485f693dc
commit 74612aefbb
55 changed files with 6019 additions and 876 deletions
@@ -60,23 +60,105 @@
'files': files,
'relFile': relFile,
'editableExts': editableExts,
'csrf_token': csrf_token,
'treeIdPrefix': 'content-tree',
'treeHeader': ta.content_files|default('Content bestanden'),
'treeEmptyText': ta.content_no_files|default('Geen bestanden gevonden.'),
'treeRouteBase': '/admin/content',
'treeRouteParams': '',
'treeDeleteRoute': '/admin/content-file-delete',
'treeDeleteName': '',
'treeDeleteValue': '',
'treeMoveRoute': '/admin/content-file-move',
'treeMoveParams': '',
'treeDirActions': true,
'treeDirSelectRoute': '/admin/content',
'treeDirRenameRoute': '',
'treeFileMove': false,
'treeHideActionsFor': [],
'selectedDir': selectedDir|default(''),
'treeRootLabel': 'content',
'treeScope': 'content',
'treeMoveUrl': '/admin/tree-move',
'treeDeleteBase': '/admin/content-file-delete',
'treeScopeParam': '',
'newFileModalId': '#newFileModal',
'newDirModalId': '#newDirModal',
} %}
{# Editor main panel #}
{# Editor / map-detail main panel #}
<div class="editor-main">
{% if showMapPaneel %}
{# ── Map detail paneel ( rechts) ── tonen wanneer een map is geselecteerd #}
<nav aria-label="breadcrumb" class="mb-2">
<ol class="breadcrumb mb-0">
<li class="breadcrumb-item"><a href="/admin/content"><i class="bi bi-folder2-open"></i> content</a></li>
{% if selectedDir %}
{% set crumbPath = '' %}
{% for part in selectedDir|split('/') %}
{% set crumbPath = crumbPath ? crumbPath ~ '/' ~ part : part %}
{% if loop.last %}
<li class="breadcrumb-item active" aria-current="page">{{ part }}</li>
{% else %}
<li class="breadcrumb-item"><a href="/admin/content?dir={{ crumbPath|url_encode }}">{{ part }}</a></li>
{% endif %}
{% endfor %}
{% endif %}
</ol>
</nav>
<div class="card shadow-sm">
<div class="card-header d-flex align-items-center gap-2">
<i class="bi bi-folder-fill text-warning"></i>
<span class="fw-semibold">{{ selectedDirIsRoot ? 'content' : selectedDirName }}</span>
{% if not selectedDirIsRoot %}
<span class="badge bg-light text-dark ms-1">content/{{ selectedDir }}</span>
{% endif %}
</div>
<div class="card-body">
{# Mapnaam bewerkbaar + Opslaan = hernoem (geen apart potlood-icoon in de boom meer) #}
<form method="POST" action="/admin/content?{% if selectedDir %}dir={{ selectedDir|url_encode }}{% endif %}" id="dir-rename-form">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
<input type="hidden" name="action" value="rename_dir">
<input type="hidden" name="dir" value="{{ selectedDir }}">
<label for="dirname" class="form-label">{{ ta.folder_name|default('Mapnaam') }}</label>
<div class="input-group mb-3">
<input type="text" class="form-control" id="dirname" name="newname" value="{{ selectedDirIsRoot ? '' : selectedDirName }}" placeholder="{{ ta.folder_name_placeholder|default('mapnaam') }}" {{ selectedDirIsRoot ? 'disabled' : '' }}>
<button type="submit" class="btn btn-primary" {{ selectedDirIsRoot ? 'disabled' : '' }}>
<i class="bi bi-check-lg"></i> {{ ta.save|default('Opslaan') }}
</button>
</div>
<div class="form-text">{{ ta.folder_rename_help|default('Bewerk de mapnaam en klik opslaan om te hernoemen.') }}</div>
</form>
{# Map acties: nieuw bestand / nieuwe map / upload — opereren op de geselecteerde map #}
<div class="d-flex flex-wrap gap-2 mb-3">
<button type="button" class="btn btn-outline-primary btn-sm" data-bs-toggle="modal" data-bs-target="#newFileModal" data-in-dir="{{ selectedDir }}" id="mapPaneelNewFileBtn">
<i class="bi bi-file-earmark-plus"></i> {{ ta.new_file|default('Nieuw bestand') }}
</button>
<button type="button" class="btn btn-outline-secondary btn-sm" data-bs-toggle="modal" data-bs-target="#newDirModal" data-in-dir="{{ selectedDir }}" id="mapPaneelNewDirBtn">
<i class="bi bi-folder-plus"></i> {{ ta.new_folder|default('Nieuwe map') }}
</button>
<button type="button" class="btn btn-outline-success btn-sm" data-bs-toggle="collapse" data-bs-target="#contentUploadForm" id="mapPaneelUploadBtn">
<i class="bi bi-cloud-upload"></i> {{ ta.upload|default('Upload') }}
</button>
{% if not selectedDirIsRoot %}
<form method="POST" action="/admin/content-dir-delete-in" class="d-inline" onsubmit="return confirm('{{ ta.confirm_delete_folder|default('Weet je zeker dat je deze map wilt verwijderen? De map moet leeg zijn.') }}')">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
<input type="hidden" name="dir" value="{{ selectedDir }}">
<button type="submit" class="btn btn-outline-danger btn-sm">
<i class="bi bi-trash"></i> {{ ta.delete|default('Verwijderen') }}
</button>
</form>
{% endif %}
</div>
{# Korte inhoud-samenvatting van de geselecteerde map #}
<div class="text-muted small">
<i class="bi bi-files"></i>
{{ selectedDirCounts.files }} {{ ta.files|default('bestanden') }},
{{ selectedDirCounts.dirs }} {{ ta.subfolders|default('submappen') }}
</div>
</div>
</div>
{% else %}
{% if relFile %}
<nav aria-label="breadcrumb" class="mb-2">
<ol class="breadcrumb mb-0">
@@ -149,15 +231,22 @@
</div>
</div>
</div>
</form>
<div class="d-flex gap-2 mt-3">
<button type="submit" class="btn btn-primary" title="{{ ta.save_ctrl_s|default('Opslaan (Ctrl+S)') }}">
<button type="submit" form="editor-form" class="btn btn-primary" title="{{ ta.save_ctrl_s|default('Opslaan (Ctrl+S)') }}">
<i class="bi bi-check-lg"></i> {{ ta.save|default('Opslaan') }}
</button>
<a href="/{{ currentLang }}{% if fileDir %}/{{ fileDir }}{% endif %}/{{ fileBaseName }}{% if fileExt != 'md' %}.{{ fileExt }}{% endif %}" target="_blank" class="btn btn-outline-info" title="{{ ta.open_new_tab|default('Open in nieuw tabblad') }}">
<i class="bi bi-eye"></i> {{ ta.preview|default('Preview') }}
</a>
<form method="POST" action="/admin/content-file-delete" class="ms-auto" onsubmit="return confirm('{{ ta.confirm_delete_file|default('Weet je zeker dat je dit bestand wilt verwijderen?') }}')">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
<input type="hidden" name="file" value="{{ relFile }}">
<button type="submit" class="btn btn-outline-danger" title="{{ ta.delete|default('Verwijderen') }}">
<i class="bi bi-trash"></i> {{ ta.delete|default('Verwijderen') }}
</button>
</form>
</div>
</form>
{% else %}
<div class="card shadow-sm">
<div class="card-body text-center py-5 text-muted">
@@ -165,11 +254,12 @@
{% if relFile %}
{{ ta.content_not_editable|default('Dit bestandstype kan niet in de editor bewerkt worden.') }}
{% else %}
{{ ta.content_select_file|default('Selecteer een bestand uit de zijbalk om te bewerken.') }}
{{ ta.content_select_file|default('Selecteer een bestand of map uit de zijbalk.') }}
{% endif %}
</div>
</div>
{% endif %}
{% endif %}{# end showMapPaneel #}
</div>
</div>
@@ -180,7 +270,7 @@
<form method="POST" action="/admin/content">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
<input type="hidden" name="action" value="new_file">
<input type="hidden" name="in_dir" id="newFileDir" value="">
<input type="hidden" name="in_dir" id="newFileDir" value="" data-in-dir-field>
<div class="modal-header">
<h5 class="modal-title" id="newFileModalLabel"><i class="bi bi-file-earmark-plus"></i> {{ ta.new_file_title|default('Nieuw bestand aanmaken') }}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
@@ -217,7 +307,7 @@
<div class="modal-content">
<form method="POST" action="/admin/content-dir-create-in">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
<input type="hidden" name="in_dir" id="newDirInDir" value="">
<input type="hidden" name="in_dir" id="newDirInDir" value="" data-in-dir-field>
<div class="modal-header">
<h5 class="modal-title" id="newDirModalLabel"><i class="bi bi-folder-plus"></i> {{ ta.new_folder_title|default('Nieuwe map aanmaken') }}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
@@ -239,36 +329,31 @@
</div>
<script>
// Pass the in-dir value from the tree button to the modal hidden fields
// Mapselectie + upload/new file/new dir binding aan de geselecteerde map.
document.addEventListener('DOMContentLoaded', function () {
var newFileModal = document.getElementById('newFileModal');
var newDirModal = document.getElementById('newDirModal');
var newFileBtnTop = document.getElementById('newFileBtnTop');
var newDirBtnTop = document.getElementById('newDirBtnTop');
var uploadDir = document.getElementById('contentUploadDir');
// Track the selected directory for the top toolbar buttons
// Track the selected directory (server-side via ?dir=). This drives the
// top toolbar buttons and the upload form target.
var selectedDir = '{{ selectedDir|default('') }}';
function updateTopButtons(dir) {
selectedDir = dir || '';
if (newFileBtnTop) newFileBtnTop.setAttribute('data-in-dir', selectedDir);
if (newDirBtnTop) newDirBtnTop.setAttribute('data-in-dir', selectedDir);
if (uploadDir) uploadDir.value = selectedDir;
}
updateTopButtons(selectedDir);
// Make folder rows selectable: clicking the folder name selects it
var tree = document.getElementById('editorFileTree');
if (tree) {
// Chevron-kliks mogen niet doorbubbellen naar de mapnaam-link (die navigeert).
tree.addEventListener('click', function (e) {
var toggle = e.target.closest('.tree-toggle');
if (!toggle) return;
// Find the in-dir from the adjacent new-file button
var row = toggle.closest('.tree-dir-row');
if (!row) return;
var newFileBtn = row.querySelector('.tree-newfile-btn');
var dir = newFileBtn ? newFileBtn.getAttribute('data-in-dir') : '';
updateTopButtons(dir);
// Visually mark the selected folder
tree.querySelectorAll('.tree-toggle.is-selected').forEach(function (el) { el.classList.remove('is-selected'); });
toggle.classList.add('is-selected');
var chevronBtn = e.target.closest('.tree-chevron-btn');
if (chevronBtn) { e.stopPropagation(); }
});
}
@@ -278,7 +363,6 @@ document.addEventListener('DOMContentLoaded', function () {
var inDir = trigger ? trigger.getAttribute('data-in-dir') : selectedDir;
var hidden = document.getElementById('newFileDir');
if (hidden) hidden.value = inDir || '';
// Show the in-dir prefix as a hint in the placeholder
var input = document.getElementById('newFilenameInput');
if (input) {
input.value = '';
@@ -295,15 +379,6 @@ document.addEventListener('DOMContentLoaded', function () {
});
}
// Mark the initially selected folder (based on the opened file's directory)
if (selectedDir && tree) {
var initialBtn = tree.querySelector('.tree-newfile-btn[data-in-dir="' + selectedDir + '"]');
if (initialBtn) {
var initialToggle = initialBtn.closest('.tree-dir-row').querySelector('.tree-toggle');
if (initialToggle) initialToggle.classList.add('is-selected');
}
}
// Layout select → update frontmatter (spiegel van content-edit.twig)
var layoutSelect = document.getElementById('layout');
if (layoutSelect) {