Files
POPS/app/templates/jobs.html
Martin Hohenberg fe594e8675 Add job creation with UUID, STL upload/library, search, job log, TZ, and CLAUDE.md
- PrintJob: job_uuid (UUID4), customer, logs relationship
- JobLog model + migration 0004 (also includes job_logs table)
- POST /jobs: upload STL or select from library, auto-logs 'Job created'
- GET /jobs?q=: search by customer or filename across all statuses
- app/stl.py: STL_UPLOAD_DIR helper (from env, default /data/stl)
- docker-compose: named volume stl_files mounted at /data/stl
- .env.install: added STL_UPLOAD_DIR and TZ=Europe/Berlin
- CLAUDE.md: full project context for future sessions

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-18 21:55:49 +02:00

171 lines
7.2 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{% extends "base.html" %}
{% block title %}Jobs — POPS{% endblock %}
{% block content %}
<div class="d-flex align-items-center justify-content-between mb-4">
<h5 class="text-white mb-0">Print Jobs</h5>
<button class="btn btn-sm btn-primary" data-bs-toggle="modal" data-bs-target="#addJobModal">
<i class="bi bi-plus-lg me-1"></i>Add Job
</button>
</div>
{# ── Search bar ── #}
<form method="GET" action="/jobs" class="mb-3">
<div class="input-group input-group-sm" style="max-width:360px">
<span class="input-group-text bg-dark border-secondary text-secondary"><i class="bi bi-search"></i></span>
<input type="text" name="q" value="{{ q }}"
class="form-control bg-dark border-secondary text-white"
placeholder="Search by customer or filename…">
{% if q %}
<a href="/jobs" class="btn btn-outline-secondary">×</a>
{% endif %}
</div>
</form>
{% if q %}
<p class="text-secondary small mb-3">Results for <strong class="text-white">{{ q }}</strong></p>
{% endif %}
<div class="card">
{% if jobs %}
<table class="table table-dark table-hover mb-0 align-middle" style="font-size:.875rem">
<thead>
<tr class="text-secondary small">
<th class="fw-normal">UUID</th>
<th class="fw-normal">Customer</th>
<th class="fw-normal">File</th>
<th class="fw-normal">Printer</th>
<th class="fw-normal">Material</th>
<th class="fw-normal">Status</th>
<th class="fw-normal text-end">Created</th>
</tr>
</thead>
<tbody>
{% for j in jobs %}
<tr>
<td class="font-monospace text-secondary" style="font-size:.75rem">
{{ j.job_uuid[:8] if j.job_uuid else '—' }}
</td>
<td class="text-white">{{ j.customer or '—' }}</td>
<td class="text-secondary">{{ j.file_name or '—' }}</td>
<td class="text-secondary">{{ j.printer.name if j.printer else '—' }}</td>
<td class="text-secondary">
{% if j.filament %}{{ j.filament.material.value }}{% if j.filament.color %} · {{ j.filament.color }}{% endif %}{% else %}—{% endif %}
</td>
<td>
{% set s = j.status.value %}
{% if s == 'printing' %}<span class="badge bg-primary">printing</span>
{% elif s == 'done' %}<span class="badge bg-success">done</span>
{% elif s == 'failed' %}<span class="badge bg-danger">failed</span>
{% elif s == 'cancelled' %}<span class="badge bg-warning text-dark">cancelled</span>
{% else %}<span class="badge bg-secondary">queued</span>
{% endif %}
</td>
<td class="text-secondary text-end">{{ j.created_at.strftime('%d.%m.%Y %H:%M') }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<div class="card-body text-secondary">
{% if q %}No jobs match "{{ q }}".{% else %}No jobs yet.{% endif %}
</div>
{% endif %}
</div>
{# ── Add Job Modal ── #}
<div class="modal fade" id="addJobModal" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content bg-dark border-secondary">
<form method="POST" action="/jobs" enctype="multipart/form-data">
<div class="modal-header border-secondary">
<h6 class="modal-title text-white">Add Print Job</h6>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body d-flex flex-column gap-3">
<div class="row g-3">
<div class="col-md-6">
<label class="form-label text-secondary small">Printer <span class="text-danger">*</span></label>
<select name="printer_id" class="form-select bg-dark border-secondary text-white" required>
<option value="">— select —</option>
{% for p in printers %}
<option value="{{ p.id }}">{{ p.name }}{% if p.location %} ({{ p.location }}){% endif %}</option>
{% endfor %}
</select>
</div>
<div class="col-md-6">
<label class="form-label text-secondary small">Customer</label>
<input type="text" name="customer" class="form-control bg-dark border-secondary text-white"
placeholder="Customer name or reference">
</div>
</div>
{# ── STL file ── #}
<div>
<label class="form-label text-secondary small">STL / File</label>
<ul class="nav nav-pills mb-2" id="stlTabs" role="tablist">
<li class="nav-item">
<button class="nav-link active py-1 px-3" style="font-size:.8rem"
data-bs-toggle="pill" data-bs-target="#uploadPane" type="button">
<i class="bi bi-upload me-1"></i>Upload
</button>
</li>
<li class="nav-item">
<button class="nav-link py-1 px-3" style="font-size:.8rem"
data-bs-toggle="pill" data-bs-target="#libraryPane" type="button">
<i class="bi bi-folder2-open me-1"></i>Library
<span class="badge bg-secondary ms-1">{{ stl_files | length }}</span>
</button>
</li>
</ul>
<div class="tab-content">
<div class="tab-pane fade show active" id="uploadPane">
<input type="file" name="stl_file"
class="form-control bg-dark border-secondary text-white"
accept=".stl,.3mf,.gcode">
</div>
<div class="tab-pane fade" id="libraryPane">
{% if stl_files %}
<select name="stl_select" class="form-select bg-dark border-secondary text-white">
<option value="">— select file —</option>
{% for f in stl_files %}
<option value="{{ f }}">{{ f }}</option>
{% endfor %}
</select>
{% else %}
<p class="text-secondary small mb-0">No files in library yet. Upload one first.</p>
{% endif %}
</div>
</div>
</div>
<div>
<label class="form-label text-secondary small">Material & Color</label>
<select name="filament_id" class="form-select bg-dark border-secondary text-white">
<option value="">— none —</option>
{% for f in filaments %}
<option value="{{ f.id }}">
{{ f.material.value }}{% if f.color %} · {{ f.color }}{% endif %}
{% if f.brand %} ({{ f.brand }}){% endif %}
</option>
{% endfor %}
</select>
</div>
<div>
<label class="form-label text-secondary small">Notes</label>
<textarea name="notes" class="form-control bg-dark border-secondary text-white"
rows="2" placeholder="Optional notes"></textarea>
</div>
</div>
<div class="modal-footer border-secondary">
<button type="button" class="btn btn-secondary btn-sm" data-bs-dismiss="modal">Cancel</button>
<button type="submit" class="btn btn-primary btn-sm">Create Job</button>
</div>
</form>
</div>
</div>
</div>
{% endblock %}