feat: organizer Android app and token-gated upload API
CI / lint-and-test (push) Failing after 1m18s

Replace Syncthing as the product upload path with a share-target app
and POST /api/jobs. Web UI live-polls job progress.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Frank Schwenk
2026-08-27 21:38:50 +02:00
parent 5dcc3717c7
commit c03f17488c
58 changed files with 3134 additions and 186 deletions
+11 -42
View File
@@ -3,61 +3,30 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#14161a">
<meta name="color-scheme" content="dark">
<title>{% block title %}{{ site_title }}{% endblock %}</title>
<link rel="icon" href="/static/favicon.svg" type="image/svg+xml">
<link rel="apple-touch-icon" href="/static/favicon.svg">
<link rel="stylesheet" href="/static/style.css">
</head>
<body>
<body{% if live is defined and live %} data-live="1"{% endif %}{% if job_id is defined %} data-job-id="{{ job_id }}"{% endif %}{% if has_active is defined and has_active %} data-live="1"{% endif %}>
<header class="site-header">
<a class="brand" href="/">{{ site_title }}</a>
<span class="header-note">Event-Pep</span>
</header>
<main>
{% block content %}{% endblock %}
</main>
<div id="lightbox" class="lightbox" hidden>
<button type="button" class="lightbox-close" aria-label="Schliessen">&times;</button>
<button type="button" class="lightbox-close" aria-label="Schließen">&times;</button>
<button type="button" class="lightbox-nav lightbox-prev" aria-label="Vorheriges Bild">‹</button>
<img id="lightbox-img" alt="">
<button type="button" class="lightbox-nav lightbox-next" aria-label="Nächstes Bild">›</button>
<p id="lightbox-caption" class="lightbox-caption"></p>
</div>
<script>
(function () {
var box = document.getElementById("lightbox");
var img = document.getElementById("lightbox-img");
if (!box || !img) return;
function openLightbox(src, alt) {
img.src = src;
img.alt = alt || "";
box.hidden = false;
document.body.classList.add("lightbox-open");
}
function closeLightbox() {
box.hidden = true;
img.removeAttribute("src");
document.body.classList.remove("lightbox-open");
}
document.addEventListener("click", function (e) {
var target = e.target;
if (!(target instanceof HTMLImageElement)) return;
if (!target.classList.contains("lightboxable")) return;
e.preventDefault();
e.stopPropagation();
var full = target.getAttribute("data-full-src");
openLightbox(full || target.currentSrc || target.src, target.alt);
});
box.addEventListener("click", function (e) {
if (e.target === box || (e.target && e.target.classList.contains("lightbox-close"))) {
closeLightbox();
}
});
document.addEventListener("keydown", function (e) {
if (e.key === "Escape" && !box.hidden) closeLightbox();
});
})();
</script>
<script src="/static/app.js" defer></script>
</body>
</html>