Compare commits
2
Commits
main
..
4599b58f98
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4599b58f98 | ||
|
|
01d5b3e001 |
No files matched your search
@@ -1,13 +0,0 @@
|
|||||||
# Copy this file to .env and fill in real values.
|
|
||||||
# .env is gitignored — never commit it.
|
|
||||||
|
|
||||||
# Password to log into /admin. Change this to something you'd actually use.
|
|
||||||
ADMIN_PASSWORD=change-me
|
|
||||||
|
|
||||||
# Random string used to sign session cookies. Generate one with:
|
|
||||||
# python3 -c "import secrets; print(secrets.token_hex(32))"
|
|
||||||
SECRET_KEY=change-me-to-a-long-random-string
|
|
||||||
|
|
||||||
# Keep "true" in production (TLS terminated by a reverse proxy in front, e.g. Pangolin).
|
|
||||||
# Only set to "false" for local testing without any TLS in front.
|
|
||||||
COOKIE_SECURE=true
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
.env
|
|
||||||
__pycache__/
|
|
||||||
*.pyc
|
|
||||||
@@ -1,270 +0,0 @@
|
|||||||
import json
|
|
||||||
import os
|
|
||||||
import re
|
|
||||||
import secrets
|
|
||||||
from functools import wraps
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
from flask import Flask, abort, redirect, render_template, request, session, url_for
|
|
||||||
|
|
||||||
app = Flask(__name__)
|
|
||||||
app.secret_key = os.environ.get("SECRET_KEY") or secrets.token_hex(32)
|
|
||||||
app.config["SESSION_COOKIE_SAMESITE"] = "Lax"
|
|
||||||
app.config["SESSION_COOKIE_SECURE"] = os.environ.get("COOKIE_SECURE", "true").lower() != "false"
|
|
||||||
|
|
||||||
ADMIN_PASSWORD = os.environ.get("ADMIN_PASSWORD")
|
|
||||||
DATA_FILE = Path(os.environ.get("DATA_FILE", "data/links.json"))
|
|
||||||
|
|
||||||
COLORS = ["blue", "green", "orange", "pink"]
|
|
||||||
|
|
||||||
DEFAULT_DATA = {
|
|
||||||
"index": {
|
|
||||||
"title": "My Links",
|
|
||||||
"subtitle": "",
|
|
||||||
"subtitle2": "",
|
|
||||||
"footer_label": "",
|
|
||||||
"footer_href": "",
|
|
||||||
"profile_image": "",
|
|
||||||
"favicon": "🍯",
|
|
||||||
"sections": [],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def load_data():
|
|
||||||
if not DATA_FILE.exists():
|
|
||||||
DATA_FILE.parent.mkdir(parents=True, exist_ok=True)
|
|
||||||
save_data(DEFAULT_DATA)
|
|
||||||
return json.loads(json.dumps(DEFAULT_DATA))
|
|
||||||
with open(DATA_FILE, "r", encoding="utf-8") as f:
|
|
||||||
return json.load(f)
|
|
||||||
|
|
||||||
|
|
||||||
def save_data(data):
|
|
||||||
tmp = DATA_FILE.with_suffix(".tmp")
|
|
||||||
with open(tmp, "w", encoding="utf-8") as f:
|
|
||||||
json.dump(data, f, ensure_ascii=False, indent=2)
|
|
||||||
tmp.replace(DATA_FILE)
|
|
||||||
|
|
||||||
|
|
||||||
def login_required(view):
|
|
||||||
@wraps(view)
|
|
||||||
def wrapped(*args, **kwargs):
|
|
||||||
if not session.get("admin"):
|
|
||||||
return redirect(url_for("admin_dashboard"))
|
|
||||||
return view(*args, **kwargs)
|
|
||||||
|
|
||||||
return wrapped
|
|
||||||
|
|
||||||
|
|
||||||
def get_page_or_404(data, page_key):
|
|
||||||
if page_key not in data:
|
|
||||||
abort(404)
|
|
||||||
return data[page_key]
|
|
||||||
|
|
||||||
|
|
||||||
def get_section_or_404(page, section_id):
|
|
||||||
for section in page["sections"]:
|
|
||||||
if section["id"] == section_id:
|
|
||||||
return section
|
|
||||||
abort(404)
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/")
|
|
||||||
def index():
|
|
||||||
data = load_data()
|
|
||||||
return render_template("page.html", page=data["index"])
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/<slug>.html")
|
|
||||||
def page(slug):
|
|
||||||
data = load_data()
|
|
||||||
page_data = data.get(slug)
|
|
||||||
if not page_data:
|
|
||||||
abort(404)
|
|
||||||
return render_template("page.html", page=page_data)
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/admin", methods=["GET"])
|
|
||||||
def admin_dashboard():
|
|
||||||
if not session.get("admin"):
|
|
||||||
return render_template("admin_login.html", error=None)
|
|
||||||
data = load_data()
|
|
||||||
return render_template("admin.html", data=data, colors=COLORS)
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/admin/login", methods=["POST"])
|
|
||||||
def admin_login():
|
|
||||||
if not ADMIN_PASSWORD:
|
|
||||||
return render_template(
|
|
||||||
"admin_login.html", error="ADMIN_PASSWORD ist nicht gesetzt (siehe .env)."
|
|
||||||
), 500
|
|
||||||
if secrets.compare_digest(request.form.get("password", ""), ADMIN_PASSWORD):
|
|
||||||
session["admin"] = True
|
|
||||||
return redirect(url_for("admin_dashboard"))
|
|
||||||
return render_template("admin_login.html", error="Falsches Passwort"), 401
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/admin/logout", methods=["POST"])
|
|
||||||
def admin_logout():
|
|
||||||
session.clear()
|
|
||||||
return redirect(url_for("admin_dashboard"))
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/admin/<page_key>/header", methods=["POST"])
|
|
||||||
@login_required
|
|
||||||
def edit_header(page_key):
|
|
||||||
data = load_data()
|
|
||||||
page = get_page_or_404(data, page_key)
|
|
||||||
page["title"] = request.form.get("title", page.get("title", "")).strip()
|
|
||||||
page["subtitle"] = request.form.get("subtitle", page.get("subtitle", "")).strip()
|
|
||||||
page["subtitle2"] = request.form.get("subtitle2", page.get("subtitle2", "")).strip()
|
|
||||||
page["footer_label"] = request.form.get("footer_label", page.get("footer_label", "")).strip()
|
|
||||||
page["footer_href"] = request.form.get("footer_href", page.get("footer_href", "")).strip()
|
|
||||||
page["profile_image"] = request.form.get("profile_image", page.get("profile_image", "")).strip()
|
|
||||||
page["favicon"] = request.form.get("favicon", page.get("favicon", "")).strip()
|
|
||||||
save_data(data)
|
|
||||||
return redirect(url_for("admin_dashboard"))
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/admin/page/add", methods=["POST"])
|
|
||||||
@login_required
|
|
||||||
def add_page():
|
|
||||||
data = load_data()
|
|
||||||
title = request.form.get("title", "").strip()
|
|
||||||
slug = request.form.get("slug", "").strip().lower()
|
|
||||||
slug = re.sub(r"[^a-z0-9-]+", "-", slug).strip("-")
|
|
||||||
if slug and title and slug not in data:
|
|
||||||
data[slug] = {
|
|
||||||
"title": title,
|
|
||||||
"subtitle": "",
|
|
||||||
"subtitle2": "",
|
|
||||||
"footer_label": "",
|
|
||||||
"footer_href": "",
|
|
||||||
"profile_image": "",
|
|
||||||
"favicon": "🍯",
|
|
||||||
"sections": [],
|
|
||||||
}
|
|
||||||
save_data(data)
|
|
||||||
return redirect(url_for("admin_dashboard"))
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/admin/page/<page_key>/delete", methods=["POST"])
|
|
||||||
@login_required
|
|
||||||
def delete_page(page_key):
|
|
||||||
if page_key == "index":
|
|
||||||
abort(400)
|
|
||||||
data = load_data()
|
|
||||||
data.pop(page_key, None)
|
|
||||||
save_data(data)
|
|
||||||
return redirect(url_for("admin_dashboard"))
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/admin/<page_key>/section/add", methods=["POST"])
|
|
||||||
@login_required
|
|
||||||
def add_section(page_key):
|
|
||||||
data = load_data()
|
|
||||||
page = get_page_or_404(data, page_key)
|
|
||||||
name = request.form.get("name", "").strip()
|
|
||||||
if name:
|
|
||||||
page["sections"].append({"id": secrets.token_hex(4), "name": name, "links": []})
|
|
||||||
save_data(data)
|
|
||||||
return redirect(url_for("admin_dashboard"))
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/admin/<page_key>/section/<section_id>/rename", methods=["POST"])
|
|
||||||
@login_required
|
|
||||||
def rename_section(page_key, section_id):
|
|
||||||
data = load_data()
|
|
||||||
page = get_page_or_404(data, page_key)
|
|
||||||
section = get_section_or_404(page, section_id)
|
|
||||||
name = request.form.get("name", "").strip()
|
|
||||||
if name:
|
|
||||||
section["name"] = name
|
|
||||||
save_data(data)
|
|
||||||
return redirect(url_for("admin_dashboard"))
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/admin/<page_key>/section/<section_id>/delete", methods=["POST"])
|
|
||||||
@login_required
|
|
||||||
def delete_section(page_key, section_id):
|
|
||||||
data = load_data()
|
|
||||||
page = get_page_or_404(data, page_key)
|
|
||||||
page["sections"] = [s for s in page["sections"] if s["id"] != section_id]
|
|
||||||
save_data(data)
|
|
||||||
return redirect(url_for("admin_dashboard"))
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/admin/<page_key>/section/<section_id>/link/add", methods=["POST"])
|
|
||||||
@login_required
|
|
||||||
def add_link(page_key, section_id):
|
|
||||||
data = load_data()
|
|
||||||
page = get_page_or_404(data, page_key)
|
|
||||||
section = get_section_or_404(page, section_id)
|
|
||||||
title = request.form.get("title", "").strip()
|
|
||||||
url = request.form.get("url", "").strip()
|
|
||||||
if title and url:
|
|
||||||
section["links"].append(
|
|
||||||
{
|
|
||||||
"id": secrets.token_hex(4),
|
|
||||||
"title": title,
|
|
||||||
"note": request.form.get("note", "").strip(),
|
|
||||||
"url": url,
|
|
||||||
"color": request.form.get("color", "blue"),
|
|
||||||
"half": bool(request.form.get("half")),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
save_data(data)
|
|
||||||
return redirect(url_for("admin_dashboard"))
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/admin/<page_key>/link/<link_id>/edit", methods=["POST"])
|
|
||||||
@login_required
|
|
||||||
def edit_link(page_key, link_id):
|
|
||||||
data = load_data()
|
|
||||||
page = get_page_or_404(data, page_key)
|
|
||||||
for section in page["sections"]:
|
|
||||||
for link in section["links"]:
|
|
||||||
if link["id"] == link_id:
|
|
||||||
link["title"] = request.form.get("title", link["title"]).strip()
|
|
||||||
link["note"] = request.form.get("note", link["note"]).strip()
|
|
||||||
link["url"] = request.form.get("url", link["url"]).strip()
|
|
||||||
link["color"] = request.form.get("color", link["color"])
|
|
||||||
link["half"] = bool(request.form.get("half"))
|
|
||||||
save_data(data)
|
|
||||||
return redirect(url_for("admin_dashboard"))
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/admin/<page_key>/link/<link_id>/delete", methods=["POST"])
|
|
||||||
@login_required
|
|
||||||
def delete_link(page_key, link_id):
|
|
||||||
data = load_data()
|
|
||||||
page = get_page_or_404(data, page_key)
|
|
||||||
for section in page["sections"]:
|
|
||||||
section["links"] = [l for l in section["links"] if l["id"] != link_id]
|
|
||||||
save_data(data)
|
|
||||||
return redirect(url_for("admin_dashboard"))
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/admin/<page_key>/link/<link_id>/move", methods=["POST"])
|
|
||||||
@login_required
|
|
||||||
def move_link(page_key, link_id):
|
|
||||||
data = load_data()
|
|
||||||
page = get_page_or_404(data, page_key)
|
|
||||||
direction = request.form.get("direction")
|
|
||||||
for section in page["sections"]:
|
|
||||||
ids = [l["id"] for l in section["links"]]
|
|
||||||
if link_id in ids:
|
|
||||||
idx = ids.index(link_id)
|
|
||||||
new_idx = idx - 1 if direction == "up" else idx + 1
|
|
||||||
if 0 <= new_idx < len(section["links"]):
|
|
||||||
section["links"][idx], section["links"][new_idx] = (
|
|
||||||
section["links"][new_idx],
|
|
||||||
section["links"][idx],
|
|
||||||
)
|
|
||||||
save_data(data)
|
|
||||||
return redirect(url_for("admin_dashboard"))
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
app.run(host="0.0.0.0", port=80)
|
|
||||||
-188
@@ -1,188 +0,0 @@
|
|||||||
{
|
|
||||||
"index": {
|
|
||||||
"title": "Honey Services for Cinnamon",
|
|
||||||
"subtitle": "Welcome!",
|
|
||||||
"subtitle2": "Here are all my important links for you!",
|
|
||||||
"footer_label": "Cinnamon Version",
|
|
||||||
"footer_href": "honey.html",
|
|
||||||
"profile_image": "https://c.tenor.com/rfib5QqA0ncAAAAd/tenor.gif",
|
|
||||||
"sections": [
|
|
||||||
{
|
|
||||||
"id": "s_important",
|
|
||||||
"name": "Important Stuff",
|
|
||||||
"links": [
|
|
||||||
{
|
|
||||||
"id": "l_crochet",
|
|
||||||
"title": "CrochetMeMaybe🧶",
|
|
||||||
"note": "Crochet Website Counting",
|
|
||||||
"url": "https://crochetmemaybe.asianxpolish.duckdns.org",
|
|
||||||
"color": "blue",
|
|
||||||
"half": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "l_pixelpaper1",
|
|
||||||
"title": "PixelPaper📖",
|
|
||||||
"note": "EBook and Manga Server",
|
|
||||||
"url": "https://pixelpaper.asianxpolish.duckdns.org",
|
|
||||||
"color": "blue",
|
|
||||||
"half": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "l_ticket1",
|
|
||||||
"title": "Ticket System 🎆",
|
|
||||||
"note": "Tickets an Honey",
|
|
||||||
"url": "https://hot.asianxpolish.duckdns.org",
|
|
||||||
"color": "blue",
|
|
||||||
"half": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "l_learn1",
|
|
||||||
"title": "Learning Tracker✒️",
|
|
||||||
"note": "Tracker für das lernen und rewards!",
|
|
||||||
"url": "https://learn.asianxpolish.duckdns.org",
|
|
||||||
"color": "blue",
|
|
||||||
"half": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "l_live",
|
|
||||||
"title": "Livestream!👾",
|
|
||||||
"note": "Dein Persönlicher Livestream",
|
|
||||||
"url": "https://live.asianxpolish.duckdns.org",
|
|
||||||
"color": "blue",
|
|
||||||
"half": false
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "s_working",
|
|
||||||
"name": "Working Stuff",
|
|
||||||
"links": [
|
|
||||||
{
|
|
||||||
"id": "l_honeycloud",
|
|
||||||
"title": "Honeycloud🍯",
|
|
||||||
"note": "The HoneyCloud Service",
|
|
||||||
"url": "https://honeyycloud.duckdns.org",
|
|
||||||
"color": "green",
|
|
||||||
"half": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "l_pocket1",
|
|
||||||
"title": "Pocket-ID📓",
|
|
||||||
"note": "Dein User in all den Systemen (Passkey)",
|
|
||||||
"url": "https://pocket.asianxpolish.duckdns.org",
|
|
||||||
"color": "green",
|
|
||||||
"half": false
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "s_tools1",
|
|
||||||
"name": "Tools",
|
|
||||||
"links": [
|
|
||||||
{
|
|
||||||
"id": "l_pdf1",
|
|
||||||
"title": "PDF Tool📝",
|
|
||||||
"note": "PDF Editieren und Zusammenfügen",
|
|
||||||
"url": "https://pdf.asianxpolish.duckdns.org",
|
|
||||||
"color": "orange",
|
|
||||||
"half": false
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"favicon": "🍯"
|
|
||||||
},
|
|
||||||
"honey": {
|
|
||||||
"title": "My Links",
|
|
||||||
"subtitle": "",
|
|
||||||
"subtitle2": "Some Stuff",
|
|
||||||
"footer_label": "Honey Version",
|
|
||||||
"footer_href": "index.html",
|
|
||||||
"profile_image": "https://c.tenor.com/rfib5QqA0ncAAAAd/tenor.gif",
|
|
||||||
"sections": [
|
|
||||||
{
|
|
||||||
"id": "s_pangolin",
|
|
||||||
"name": "Public with Pangolin",
|
|
||||||
"links": [
|
|
||||||
{
|
|
||||||
"id": "l_pixelpaper2",
|
|
||||||
"title": "PixelPaper📖",
|
|
||||||
"note": "EBook and Manga Server",
|
|
||||||
"url": "https://pixelpaper.asianxpolish.duckdns.org",
|
|
||||||
"color": "orange",
|
|
||||||
"half": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "l_catto",
|
|
||||||
"title": "Catto🐈⬛",
|
|
||||||
"note": "",
|
|
||||||
"url": "https://catto.asianxpolish.duckdns.org",
|
|
||||||
"color": "orange",
|
|
||||||
"half": true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "s_tools2",
|
|
||||||
"name": "Tools",
|
|
||||||
"links": [
|
|
||||||
{
|
|
||||||
"id": "l_pdf2",
|
|
||||||
"title": "PDF Tool📝",
|
|
||||||
"note": "PDF Editieren und Zusammenfügen",
|
|
||||||
"url": "https://pdf.asianxpolish.duckdns.org",
|
|
||||||
"color": "pink",
|
|
||||||
"half": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "l_pocket2",
|
|
||||||
"title": "Pocket-ID",
|
|
||||||
"note": "OIDC Passkey",
|
|
||||||
"url": "https://pocket.asianxpolish.duckdns.org",
|
|
||||||
"color": "green",
|
|
||||||
"half": false
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "s_cinnamon",
|
|
||||||
"name": "For Cinnamon",
|
|
||||||
"links": [
|
|
||||||
{
|
|
||||||
"id": "l_crochet2",
|
|
||||||
"title": "CrochetMeMaybe🧶",
|
|
||||||
"note": "Crochet Website Counting",
|
|
||||||
"url": "https://crochetmemaybe.asianxpolish.duckdns.org",
|
|
||||||
"color": "blue",
|
|
||||||
"half": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "l_ticket2",
|
|
||||||
"title": "Ticket System 🎆",
|
|
||||||
"note": "",
|
|
||||||
"url": "https://hot.asianxpolish.duckdns.org/admin.html",
|
|
||||||
"color": "blue",
|
|
||||||
"half": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "l_learn2",
|
|
||||||
"title": "Learning Tracker✒️",
|
|
||||||
"note": "",
|
|
||||||
"url": "https://learn.asianxpolish.duckdns.org",
|
|
||||||
"color": "blue",
|
|
||||||
"half": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "32be2487",
|
|
||||||
"title": "Admin Page",
|
|
||||||
"note": "",
|
|
||||||
"url": "https://asianxpolish.duckdns.org/admin",
|
|
||||||
"color": "blue",
|
|
||||||
"half": false
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"favicon": "🍯"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
services:
|
|
||||||
honey-links:
|
|
||||||
image: gitea.honeyy.duckdns.org/website/honey-links:latest
|
|
||||||
ports:
|
|
||||||
- "8080:80"
|
|
||||||
environment:
|
|
||||||
- ADMIN_PASSWORD=${ADMIN_PASSWORD}
|
|
||||||
- SECRET_KEY=${SECRET_KEY}
|
|
||||||
# Set to "false" only for local http-only testing without a TLS proxy in front.
|
|
||||||
- COOKIE_SECURE=${COOKIE_SECURE:-true}
|
|
||||||
volumes:
|
|
||||||
- ./data:/app/data
|
|
||||||
restart: unless-stopped
|
|
||||||
+9
-14
@@ -1,21 +1,16 @@
|
|||||||
# Use the official Python image
|
# Use the official Python image
|
||||||
FROM python:3.14-slim
|
FROM python:3.9-slim
|
||||||
|
|
||||||
# Set the working directory in the container
|
# Set the working directory in the container
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
# Install dependencies
|
# Copy the HTML files into the container
|
||||||
COPY requirements.txt .
|
COPY index.html /app
|
||||||
RUN pip install --no-cache-dir -r requirements.txt
|
COPY honey.html /app
|
||||||
|
COPY styles.css /app
|
||||||
|
|
||||||
# Copy the application into the container
|
# Expose port 8000
|
||||||
COPY app.py .
|
EXPOSE 8000
|
||||||
COPY templates/ templates/
|
|
||||||
COPY static/ static/
|
|
||||||
COPY data/ data/
|
|
||||||
|
|
||||||
# Expose port 80
|
# Start the Python HTTP server on port 80
|
||||||
EXPOSE 80
|
CMD ["python3", "-m", "http.server", "80"]
|
||||||
|
|
||||||
# Start the app via a production WSGI server
|
|
||||||
CMD ["waitress-serve", "--host=0.0.0.0", "--port=80", "app:app"]
|
|
||||||
+74
@@ -0,0 +1,74 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||||
|
<title>Honey | Links</title>
|
||||||
|
<link rel="icon" type="image/x-icon" href="/images/favicon/favicon.ico">
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600&display=swap" rel="stylesheet"/>
|
||||||
|
<link rel="stylesheet" href="styles.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="background-glow"></div>
|
||||||
|
|
||||||
|
<!-- Fireflies container -->
|
||||||
|
<div class="fireflies">
|
||||||
|
<div class="firefly"></div>
|
||||||
|
<div class="firefly"></div>
|
||||||
|
<div class="firefly"></div>
|
||||||
|
<div class="firefly"></div>
|
||||||
|
<div class="firefly"></div>
|
||||||
|
<div class="firefly"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Content on top -->
|
||||||
|
<img src="https://c.tenor.com/rfib5QqA0ncAAAAd/tenor.gif" alt="Profile Picture" class="profile-pic" />
|
||||||
|
<h1 class="fade-in">My Links</h1>
|
||||||
|
<p class="bio2 fade-in">Some Stuff</p>
|
||||||
|
|
||||||
|
|
||||||
|
<h2 class="section-title">Public with Pangolin</h2>
|
||||||
|
|
||||||
|
<a href="https://pixelpaper.asianxpolish.duckdns.org" class="link animated-link orange-glow" target="_blank">
|
||||||
|
<div class="link-title">PixelPaper📖</div>
|
||||||
|
<div class="link-note">EBook and Manga Server</div>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<a href="https://catto.asianxpolish.duckdns.org" class="link animated-link orange-glow" target="_blank">
|
||||||
|
<div class="link-title">Catto</div>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<h2 class="section-title">VPN Netbird</h2>
|
||||||
|
|
||||||
|
<a href="https://honeyydashboard.duckdns.org" class="link animated-link pink-glow" target="_blank">
|
||||||
|
<div class="link-title">Dashboard🦞</div>
|
||||||
|
<div class="link-note">Perosnal Dashboard</div>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<a href="https://pdf.honeyy.duckdns.org" class="link animated-link pink-glow" target="_blank">
|
||||||
|
<div class="link-title">PDF Tool📝</div>
|
||||||
|
<div class="link-note">PDF Editieren und Zusammenfügen</div>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
|
||||||
|
<h2 class="section-title">For Cinnamon</h2>
|
||||||
|
|
||||||
|
<a href="https://crochetmemaybe.asianxpolish.duckdns.org" class="link animated-link blue-glow" target="_blank">
|
||||||
|
<div class="link-title">CrochetMeMaybe🧶</div>
|
||||||
|
<div class="link-note">Crochet Website Counting</div>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<a href="https://hot.asianxpolish.duckdns.org/admin.html" class="link animated-link blue-glow" target="_blank">
|
||||||
|
<div class="link-title">Ticket System 🎆</div>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
|
||||||
|
<footer>
|
||||||
|
<a href="index.html"target="_self" title="Switch to Cinnamon Version" class="link-footer">
|
||||||
|
<div>Honey Version</div>
|
||||||
|
</a>
|
||||||
|
© 2025 Marc Luv
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
+75
@@ -0,0 +1,75 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||||
|
<title>Honey | Links</title>
|
||||||
|
<link rel="icon" type="image/x-icon" href="/images/favicon/favicon.ico">
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600&display=swap" rel="stylesheet"/>
|
||||||
|
<link rel="stylesheet" href="styles.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="background-glow"></div>
|
||||||
|
|
||||||
|
<!-- Fireflies container -->
|
||||||
|
<div class="fireflies">
|
||||||
|
<div class="firefly"></div>
|
||||||
|
<div class="firefly"></div>
|
||||||
|
<div class="firefly"></div>
|
||||||
|
<div class="firefly"></div>
|
||||||
|
<div class="firefly"></div>
|
||||||
|
<div class="firefly"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Content on top -->
|
||||||
|
<img src="https://c.tenor.com/rfib5QqA0ncAAAAd/tenor.gif" alt="Profile Picture" class="profile-pic" />
|
||||||
|
<h1 class="fade-in">Honey Services for Cinnamon</h1>
|
||||||
|
<p class="bio fade-in">Welcome!</p>
|
||||||
|
<p class="bio2 fade-in">Here are all my important links for you!</p>
|
||||||
|
|
||||||
|
|
||||||
|
<h2 class="section-title">Public Sites (No VPN)</h2>
|
||||||
|
|
||||||
|
<a href="https://crochetmemaybe.asianxpolish.duckdns.org" class="link animated-link blue-glow" target="_blank">
|
||||||
|
<div class="link-title">CrochetMeMaybe🧶</div>
|
||||||
|
<div class="link-note">Crochet Website Counting</div>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<a href="https://pixelpaper.asianxpolish.duckdns.org" class="link animated-link blue-glow" target="_blank">
|
||||||
|
<div class="link-title">PixelPaper📖</div>
|
||||||
|
<div class="link-note">EBook and Manga Server</div>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<a href="https://hot.asianxpolish.duckdns.org" class="link animated-link blue-glow" target="_blank">
|
||||||
|
<div class="link-title">Ticket System 🎆</div>
|
||||||
|
<div class="link-note">Tickets an Honey</div>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<h2 class="section-title">My Links (With VPN)</h2>
|
||||||
|
|
||||||
|
<a href="https://honeyycloud.duckdns.org" class="link animated-link green-glow" target="_blank">
|
||||||
|
<div class="link-title">Honeycloud🍯</div>
|
||||||
|
<div class="link-note">The HoneyCloud Service</div>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<a href="https://honeyydashboard.duckdns.org" class="link animated-link green-glow" target="_blank">
|
||||||
|
<div class="link-title">Dashboard🦞</div>
|
||||||
|
<div class="link-note">Perosnal Dashboard</div>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<h2 class="section-title">Tools</h2>
|
||||||
|
|
||||||
|
<a href="https://pdf.honeyy.duckdns.org" class="link animated-link orange-glow" target="_blank">
|
||||||
|
<div class="link-title">PDF Tool📝</div>
|
||||||
|
<div class="link-note">PDF Editieren und Zusammenfügen</div>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<footer>
|
||||||
|
<a href="honey.html"target="_self" title="Switch to Honey Version" class="link-footer">
|
||||||
|
<div>Cinnamon Version</div>
|
||||||
|
</a>
|
||||||
|
© 2025 Marc Luv
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
Flask>=3.0,<4
|
|
||||||
waitress>=3.0,<4
|
|
||||||
@@ -144,18 +144,11 @@ animation: pulseGlow 3s ease-in-out infinite;
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.links-grid {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 10px;
|
|
||||||
width: 100%;
|
|
||||||
max-width: 320px;
|
|
||||||
margin-bottom: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.link {
|
.link {
|
||||||
display: block;
|
display: block;
|
||||||
flex: 0 0 100%;
|
width: 100%;
|
||||||
|
max-width: 320px;
|
||||||
|
margin: 6px 0;
|
||||||
padding: 14px 24px;
|
padding: 14px 24px;
|
||||||
background-color: rgba(255, 255, 255, 0.04);
|
background-color: rgba(255, 255, 255, 0.04);
|
||||||
color: #f3f4f6;
|
color: #f3f4f6;
|
||||||
@@ -167,12 +160,6 @@ font-weight: 600;
|
|||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Opt-in: two links next to each other instead of full width */
|
|
||||||
.link.half {
|
|
||||||
flex: 0 0 calc(50% - 5px);
|
|
||||||
padding: 14px 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.link:hover {
|
.link:hover {
|
||||||
background-color: rgba(255, 255, 255, 0.07); /* softer highlight */
|
background-color: rgba(255, 255, 255, 0.07); /* softer highlight */
|
||||||
color: #e0e7ff; /* gentle light blue tint */
|
color: #e0e7ff; /* gentle light blue tint */
|
||||||
@@ -320,10 +307,6 @@ will-change: opacity, transform, filter;
|
|||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
padding: 12px 20px;
|
padding: 12px 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.link.half {
|
|
||||||
flex-basis: 100%;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
@@ -1,217 +0,0 @@
|
|||||||
<!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>/<slug>.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>
|
|
||||||
<div>
|
|
||||||
<label class="checkbox">Profilbild-URL</label>
|
|
||||||
<input type="text" name="profile_image" value="{{ page.profile_image or '' }}" placeholder="https://...">
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label class="checkbox">Favicon (Emoji)</label>
|
|
||||||
<input type="text" name="favicon" value="{{ page.favicon or '' }}" placeholder="🍯" style="width: 60px;">
|
|
||||||
</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>
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="de">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
|
||||||
<title>Admin Login</title>
|
|
||||||
<link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}">
|
|
||||||
<style>
|
|
||||||
.admin-box {
|
|
||||||
max-width: 320px;
|
|
||||||
margin: 80px auto;
|
|
||||||
padding: 24px;
|
|
||||||
background: rgba(255, 255, 255, 0.04);
|
|
||||||
border: 1px solid #4b5563;
|
|
||||||
border-radius: 10px;
|
|
||||||
}
|
|
||||||
.admin-box label { display: block; margin-bottom: 6px; color: #9ca3af; font-size: 13px; }
|
|
||||||
.admin-box input {
|
|
||||||
width: 100%;
|
|
||||||
padding: 10px;
|
|
||||||
margin-bottom: 16px;
|
|
||||||
border-radius: 6px;
|
|
||||||
border: 1px solid #4b5563;
|
|
||||||
background: #0f172a;
|
|
||||||
color: #f3f4f6;
|
|
||||||
}
|
|
||||||
.admin-box button {
|
|
||||||
width: 100%;
|
|
||||||
padding: 10px;
|
|
||||||
border-radius: 6px;
|
|
||||||
border: none;
|
|
||||||
background: #21e4d3;
|
|
||||||
color: #0f172a;
|
|
||||||
font-weight: 600;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
.admin-box button:hover { background: #24ffed; }
|
|
||||||
.admin-error { color: #fca5a5; margin-bottom: 12px; font-size: 14px; }
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="admin-box">
|
|
||||||
<h1 style="font-size: 20px; margin-bottom: 16px; text-align: left;">Admin Login</h1>
|
|
||||||
{% if error %}<div class="admin-error">{{ error }}</div>{% endif %}
|
|
||||||
<form method="post" action="{{ url_for('admin_login') }}">
|
|
||||||
<label for="password">Passwort</label>
|
|
||||||
<input type="password" id="password" name="password" autofocus required>
|
|
||||||
<button type="submit">Einloggen</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
|
||||||
<title>Honey | Links</title>
|
|
||||||
<link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>{{ page.favicon or '🍯' }}</text></svg>">
|
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600&display=swap" rel="stylesheet"/>
|
|
||||||
<link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}">
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="background-glow"></div>
|
|
||||||
|
|
||||||
<!-- Fireflies container -->
|
|
||||||
<div class="fireflies">
|
|
||||||
<div class="firefly"></div>
|
|
||||||
<div class="firefly"></div>
|
|
||||||
<div class="firefly"></div>
|
|
||||||
<div class="firefly"></div>
|
|
||||||
<div class="firefly"></div>
|
|
||||||
<div class="firefly"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Content on top -->
|
|
||||||
{% if page.profile_image %}<img src="{{ page.profile_image }}" alt="Profile Picture" class="profile-pic" />{% endif %}
|
|
||||||
<h1 class="fade-in">{{ page.title }}</h1>
|
|
||||||
{% if page.subtitle %}<p class="bio fade-in">{{ page.subtitle }}</p>{% endif %}
|
|
||||||
{% if page.subtitle2 %}<p class="bio2 fade-in">{{ page.subtitle2 }}</p>{% endif %}
|
|
||||||
|
|
||||||
{% for section in page.sections %}
|
|
||||||
<h2 class="section-title">{{ section.name }}</h2>
|
|
||||||
<div class="links-grid">
|
|
||||||
{% for link in section.links %}
|
|
||||||
<a href="{{ link.url }}" class="link animated-link {{ link.color }}-glow{% if link.half %} half{% endif %}" target="_blank" style="--i: {{ loop.index0 }}">
|
|
||||||
<div class="link-title">{{ link.title }}</div>
|
|
||||||
{% if link.note %}<div class="link-note">{{ link.note }}</div>{% endif %}
|
|
||||||
</a>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
{% endfor %}
|
|
||||||
|
|
||||||
<footer>
|
|
||||||
<a href="{{ page.footer_href }}" target="_self" title="Switch Version" class="link-footer">
|
|
||||||
<div>{{ page.footer_label }}</div>
|
|
||||||
</a>
|
|
||||||
© 2025 Marc Luv
|
|
||||||
</footer>
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
Reference in new issue
Block a user