feat: organizer Android app and token-gated upload API
CI / lint-and-test (push) Failing after 1m18s
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:
+11
-42
@@ -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">×</button>
|
||||
<button type="button" class="lightbox-close" aria-label="Schließen">×</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>
|
||||
|
||||
@@ -2,28 +2,34 @@
|
||||
{% block title %}{{ site_title }}{% endblock %}
|
||||
{% block content %}
|
||||
{% if not jobs %}
|
||||
<p class="empty-state">Noch keine Jobs. Bild in den Sync-Ordner legen, dann kurz warten.</p>
|
||||
<p class="empty-state">Noch keine Jobs. Foto in der App teilen, dann kurz warten.</p>
|
||||
{% else %}
|
||||
<ul class="job-grid">
|
||||
{% for job in jobs %}
|
||||
<li class="job-card">
|
||||
<li class="job-card{% if job.status in ACTIVE_STATUSES %} is-live{% endif %}">
|
||||
<a href="/jobs/{{ job.job_id }}">
|
||||
{% if job.thumbnail %}
|
||||
{% set img = img_attrs(job.job_id, job.thumbnail) %}
|
||||
<img class="thumb lightboxable"
|
||||
<img class="thumb"
|
||||
src="{{ img.src }}"
|
||||
srcset="{{ img.srcset }}"
|
||||
sizes="{{ img.sizes }}"
|
||||
data-full-src="{{ img.full_src }}"
|
||||
alt="Variante von Job {{ job.job_id }}"
|
||||
loading="lazy"
|
||||
decoding="async">
|
||||
{% else %}
|
||||
<span class="thumb thumb-placeholder status-{{ job.status }}">{{ job.status }}</span>
|
||||
<span class="thumb thumb-placeholder status-{{ job.status }}">{{ job.status_label }}</span>
|
||||
{% endif %}
|
||||
<span class="job-meta">
|
||||
<span class="job-id">{{ job.job_id }}</span>
|
||||
<span class="job-status status-{{ job.status }}">{{ job.status }} · {{ job.variant_count }} Varianten</span>
|
||||
<span class="job-status status-{{ job.status }}">
|
||||
{{ job.status_label }}
|
||||
{% if job.stage_label and job.status in ACTIVE_STATUSES %}
|
||||
· {{ job.stage_label }}
|
||||
{% else %}
|
||||
· {{ job.variant_count }} Varianten
|
||||
{% endif %}
|
||||
</span>
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
+16
-2
@@ -3,8 +3,20 @@
|
||||
{% block content %}
|
||||
<p class="back-link"><a href="/">← Alle Jobs</a></p>
|
||||
|
||||
<h1>Job {{ job_id }}</h1>
|
||||
<p class="job-status status-{{ status.status }}">Status: {{ status.status or "unbekannt" }}</p>
|
||||
<div class="job-heading">
|
||||
<h1>Job {{ job_id }}</h1>
|
||||
<p class="job-status status-{{ status.status }}">
|
||||
{{ status_label(status.status) }}
|
||||
{% if status.status in ACTIVE_STATUSES %}
|
||||
· {{ stage_label(status.status, status.stage, status.progress) }}
|
||||
{% endif %}
|
||||
</p>
|
||||
</div>
|
||||
{% if status.status in ACTIVE_STATUSES and status.progress and status.progress.total %}
|
||||
<div class="progress" aria-hidden="true">
|
||||
<span style="width: {{ (status.progress.current / status.progress.total * 100) | int }}%"></span>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if status.error %}
|
||||
<p class="error-box">Fehler: {{ status.error }}</p>
|
||||
{% endif %}
|
||||
@@ -28,6 +40,8 @@
|
||||
<a href="/jobs/{{ job_id }}/files/{{ original_name }}" download>Download</a>
|
||||
</figcaption>
|
||||
</figure>
|
||||
{% else %}
|
||||
<p class="empty-state">Original kommt, sobald der Worker greift.</p>
|
||||
{% endif %}
|
||||
{% if has_rembg and rembg_name %}
|
||||
{% set img = img_attrs(job_id, rembg_name, sizes=PAIR_SIZES) %}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Remix {{ job_id }} — {{ site_title }}{% endblock %}
|
||||
{% block content %}
|
||||
<p class="back-link"><a href="/jobs/{{ job_id }}">← Zurueck zum Job</a></p>
|
||||
<p class="back-link"><a href="/jobs/{{ job_id }}">← Zurück zum Job</a></p>
|
||||
|
||||
<h1>Remix · Job {{ job_id }}</h1>
|
||||
{% if from_variant %}
|
||||
|
||||
Reference in New Issue
Block a user