Convert to Flask app with password-protected admin panel

Links now live in data/links.json instead of hardcoded HTML, editable
via /admin (add/rename/reorder/delete sections and links, per-link
color and half-width side-by-side layout). New pages can be added
without code changes. Docker image only needs rebuilding on code
changes now, not on content changes (data/ is volume-mounted).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
marcandClaude Sonnet 5 committed 2026-07-23 22:50:05 +02:00
1 parent 56dc37843a
commit 61c4701115
13 files changed
+797 -173

No files matched your search

+209
View File
@@ -0,0 +1,209 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>Admin – Honey Links</title>
<link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}">
<style>
body {
display: block;
padding: 24px;
max-width: 900px;
margin: 0 auto;
overflow-x: visible;
}
h1 { text-align: left; }
h2.section-title { animation: none; }
.top-bar { display: flex; justify-content: space-between; align-items: center; margin-bottom: 24px; }
.admin-page { margin-bottom: 40px; border: 1px solid #374151; border-radius: 10px; padding: 20px; }
.admin-page h2 { color: #a5f3fc; margin-bottom: 16px; }
.admin-section { border: 1px solid #374151; border-radius: 8px; padding: 12px; margin-bottom: 16px; }
.section-head { display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 8px; margin-bottom: 8px; }
.admin-link-row { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; padding: 8px 0; border-bottom: 1px solid #1f2937; }
.admin-link-row:last-of-type { border-bottom: none; }
.admin-link-row form.edit-form {
display: flex;
flex-wrap: wrap;
gap: 6px;
align-items: center;
flex: 1 1 100%;
}
.admin-link-row .link-actions {
display: flex;
gap: 6px;
align-items: center;
}
form.inline { display: inline-flex; gap: 6px; align-items: center; }
input[type=text], input[type=url], input[type=password] {
padding: 6px 8px;
border-radius: 4px;
border: 1px solid #4b5563;
background: #0f172a;
color: #f3f4f6;
}
select {
padding: 6px 8px;
border-radius: 4px;
background: #0f172a;
color: #f3f4f6;
border: 1px solid #4b5563;
}
button {
padding: 6px 12px;
border-radius: 4px;
border: 1px solid #4b5563;
background: rgba(255, 255, 255, 0.06);
color: #f3f4f6;
cursor: pointer;
white-space: nowrap;
}
button:hover { background: rgba(255, 255, 255, 0.12); }
.btn-danger { border-color: #b91c1c; color: #fca5a5; }
.new-form { margin-top: 12px; padding-top: 12px; border-top: 1px dashed #374151; }
label.checkbox { display: flex; align-items: center; gap: 4px; font-size: 13px; color: #9ca3af; white-space: nowrap; }
.field-title { flex: 1 1 130px; min-width: 100px; }
.field-note { flex: 1 1 150px; min-width: 100px; }
.field-url { flex: 2 1 180px; min-width: 140px; }
.quick-nav { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 24px; }
.quick-nav a {
padding: 6px 12px;
border-radius: 999px;
border: 1px solid #4b5563;
background: rgba(255, 255, 255, 0.04);
color: #a5f3fc;
text-decoration: none;
font-size: 13px;
}
.quick-nav a:hover { background: rgba(255, 255, 255, 0.1); }
.view-link { font-size: 13px; color: #21e4d3; text-decoration: none; margin-left: 10px; white-space: nowrap; }
.view-link:hover { color: #24ffed; }
</style>
</head>
<body>
<div class="top-bar">
<h1>Admin</h1>
<form method="post" action="{{ url_for('admin_logout') }}"><button type="submit">Logout</button></form>
</div>
<div class="quick-nav">
{% for page_key in data.keys() %}
<a href="#page-{{ page_key }}">↓ {{ page_key }}</a>
{% endfor %}
</div>
<div class="admin-page">
<h2>Neue Seite anlegen</h2>
<form method="post" action="{{ url_for('add_page') }}" style="display: flex; gap: 8px; flex-wrap: wrap; align-items: center;">
<input type="text" name="title" placeholder="Titel der Seite" required>
<input type="text" name="slug" placeholder="url-slug (z.B. gaming)" pattern="[a-zA-Z0-9-]+" required>
<button type="submit">+ Seite anlegen</button>
</form>
<p style="color:#9ca3af; font-size:13px; margin-top:8px;">Erreichbar danach unter <code>/&lt;slug&gt;.html</code>, z.B. <code>/gaming.html</code>.</p>
</div>
{% for page_key, page in data.items() %}
<div class="admin-page" id="page-{{ page_key }}">
<div class="section-head">
<h2 style="margin-bottom:0;">
Seite: {{ page_key }} — erreichbar unter {% if page_key == 'index' %}<code>/</code>{% else %}<code>/{{ page_key }}.html</code>{% endif %}
<a class="view-link" href="{{ '/' if page_key == 'index' else '/' + page_key + '.html' }}" target="_blank">🔗 ansehen</a>
</h2>
{% if page_key != 'index' %}
<form method="post" action="{{ url_for('delete_page', page_key=page_key) }}" onsubmit="return confirm('Seite \'{{ page_key }}\' komplett inkl. aller Sections/Links löschen?');">
<button type="submit" class="btn-danger">Seite löschen</button>
</form>
{% endif %}
</div>
<form method="post" action="{{ url_for('edit_header', page_key=page_key) }}" style="margin-bottom: 20px; display: flex; gap: 10px; flex-wrap: wrap; align-items: flex-end;">
<div>
<label class="checkbox">Titel</label>
<input type="text" name="title" value="{{ page.title }}">
</div>
<div>
<label class="checkbox">Untertitel 1</label>
<input type="text" name="subtitle" value="{{ page.subtitle or '' }}">
</div>
<div>
<label class="checkbox">Untertitel 2</label>
<input type="text" name="subtitle2" value="{{ page.subtitle2 or '' }}">
</div>
<div>
<label class="checkbox">Footer-Link-Ziel</label>
<input type="text" name="footer_href" value="{{ page.footer_href or '' }}" placeholder="z.B. honey.html">
</div>
<div>
<label class="checkbox">Footer-Link-Text</label>
<input type="text" name="footer_label" value="{{ page.footer_label or '' }}" placeholder="z.B. Honey Version">
</div>
<button type="submit">Header speichern</button>
</form>
{% for section in page.sections %}
<div class="admin-section">
<div class="section-head">
<form method="post" action="{{ url_for('rename_section', page_key=page_key, section_id=section.id) }}" class="inline">
<input type="text" name="name" value="{{ section.name }}">
<button type="submit">Umbenennen</button>
</form>
<form method="post" action="{{ url_for('delete_section', page_key=page_key, section_id=section.id) }}" onsubmit="return confirm('Section \'{{ section.name }}\' inkl. aller Links wirklich löschen?');">
<button type="submit" class="btn-danger">Section löschen</button>
</form>
</div>
{% for link in section.links %}
<div class="admin-link-row">
<form method="post" action="{{ url_for('edit_link', page_key=page_key, link_id=link.id) }}" class="edit-form">
<input class="field-title" type="text" name="title" value="{{ link.title }}">
<input class="field-note" type="text" name="note" value="{{ link.note or '' }}" placeholder="Notiz">
<input class="field-url" type="url" name="url" value="{{ link.url }}">
<select name="color">
{% for c in colors %}
<option value="{{ c }}" {% if link.color == c %}selected{% endif %}>{{ c }}</option>
{% endfor %}
</select>
<label class="checkbox"><input type="checkbox" name="half" {% if link.half %}checked{% endif %}> nebeneinander</label>
<div class="link-actions">
<button type="submit">Speichern</button>
</div>
</form>
<div class="link-actions">
<form method="post" action="{{ url_for('move_link', page_key=page_key, link_id=link.id) }}" class="inline">
<input type="hidden" name="direction" value="up">
<button type="submit" title="nach oben">↑</button>
</form>
<form method="post" action="{{ url_for('move_link', page_key=page_key, link_id=link.id) }}" class="inline">
<input type="hidden" name="direction" value="down">
<button type="submit" title="nach unten">↓</button>
</form>
<form method="post" action="{{ url_for('delete_link', page_key=page_key, link_id=link.id) }}" class="inline" onsubmit="return confirm('Link \'{{ link.title }}\' wirklich löschen?');">
<button type="submit" class="btn-danger">Löschen</button>
</form>
</div>
</div>
{% endfor %}
<form method="post" action="{{ url_for('add_link', page_key=page_key, section_id=section.id) }}" class="new-form" style="display: flex; gap: 8px; flex-wrap: wrap; align-items: center;">
<input class="field-title" type="text" name="title" placeholder="Titel" required>
<input class="field-note" type="text" name="note" placeholder="Notiz (optional)">
<input class="field-url" type="url" name="url" placeholder="https://..." required>
<select name="color">
{% for c in colors %}
<option value="{{ c }}">{{ c }}</option>
{% endfor %}
</select>
<label class="checkbox"><input type="checkbox" name="half"> nebeneinander (halbe Breite)</label>
<button type="submit">+ Link hinzufügen</button>
</form>
</div>
{% endfor %}
<form method="post" action="{{ url_for('add_section', page_key=page_key) }}" style="margin-top: 12px; display: flex; gap: 8px;">
<input type="text" name="name" placeholder="Neue Section" required>
<button type="submit">+ Section hinzufügen</button>
</form>
</div>
{% endfor %}
</body>
</html>