Watch EPIRent version and archive its download packages

- Poll the public download page for "Aktuelle Version <nr> vom <date>",
  report changes through the normal change feed and notification mail,
  and show the current version as a card on the overview. Runs on its own
  interval (default 30 min); the first run only records a baseline.
- Archive epirentServer.zip / epirentClient.zip per version number so we
  keep a download cache and history: streamed to disk with SHA-256 and
  size, fetched in a background thread so ticket polling is unaffected,
  with a free-space check before downloading (~1.1 GB per version).
- Retention is configurable via EPIRENT_DOWNLOAD_KEEP, where 0 means
  unlimited; a successful archive is reported as a change and by mail.
- New Downloads page listing the archived versions with checksums and
  links to fetch the cached files.
- Notification lines now omit the arrow when there is no previous value
  and drop the "#" for entries without a ticket.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-20 15:12:48 +02:00
co-authored by Claude Opus 4.8
parent 528d66b2b0
commit bdcb17285c
13 changed files with 575 additions and 10 deletions
+4 -2
View File
@@ -2,9 +2,11 @@
<li class="{{ 'unseen' if not c.seen else '' }}"
{% if c.detail %}title="{{ c.detail }}"{% endif %}>
<span class="tag tag-{{ c.field }}">{{ c.label }}</span>
<a class="cnum" href="{{ url_for('ticket_detail', ticket_id=c.ticket_id) }}">#{{ c.ticket_number }}</a>
{% if c.ticket_id %}
<a class="cnum" href="{{ url_for('ticket_detail', ticket_id=c.ticket_id) }}">#{{ c.ticket_number }}</a>
{% endif %}
<span class="ctitle">{{ c.ticket_title }}</span>
<span class="cchg">{{ c.old_value or '' }} → <b>{{ c.new_value or '' }}</b></span>
<span class="cchg">{% if c.old_value %}{{ c.old_value }} → {% endif %}<b>{{ c.new_value or '' }}</b></span>
<time>{{ c.detected_at|dt }}</time>
{% if not c.seen %}
<form method="post" action="{{ url_for('mark_one_read', change_id=c.id) }}" class="mark-one">
+1
View File
@@ -13,6 +13,7 @@
<a href="{{ url_for('index') }}">Übersicht</a>
<a href="{{ url_for('analytics_view') }}">Auswertung</a>
<a href="{{ url_for('changes') }}">Änderungen</a>
<a href="{{ url_for('downloads_view') }}">Downloads</a>
<a href="{{ url_for('new_ticket_form') }}">+ Neues Ticket</a>
<a href="{{ zammad_url }}" target="_blank" rel="noopener">Zammad&nbsp;</a>
</nav>
+41
View File
@@ -0,0 +1,41 @@
{% extends "base.html" %}
{% block title %}Downloads · EPI-Support{% endblock %}
{% block content %}
<section class="panel">
<div class="panel-head">
<h2>EPIRent Download-Archiv</h2>
{% if current %}
<span class="hint-inline">aktuelle Version: <b>{{ current.version }}</b> vom {{ current.released_at }}</span>
{% endif %}
</div>
{% if versions %}
{% for v in versions %}
<div class="dl-version">
<div class="dl-head">
<span class="dl-ver">Version {{ v.version }}</span>
<span class="dl-meta">{{ human(v.total) }} · gesichert am {{ v.downloaded_at|dt }}</span>
</div>
<ul class="dl-files">
{% for f in v.files %}
<li>
<a href="{{ url_for('download_file', version=v.version, filename=f.filename) }}">⬇ {{ f.filename }}</a>
<span class="dl-size">{{ human(f.size) }}</span>
{% if f.sha256 %}<code class="dl-hash" title="SHA-256">{{ f.sha256[:16] }}…</code>{% endif %}
</li>
{% endfor %}
</ul>
</div>
{% endfor %}
{% if keep and keep > 0 %}
<p class="hint">Es werden die letzten <b>{{ keep }}</b> Versionsstände aufbewahrt; ältere werden automatisch gelöscht.</p>
{% else %}
<p class="hint">Aufbewahrung: <b>unbegrenzt</b> es wird nichts automatisch gelöscht (ca. 1,1 GB je Stand).</p>
{% endif %}
{% else %}
<p class="muted">Noch keine Pakete archiviert. Der Poller lädt sie im Hintergrund,
sobald eine Version bekannt ist (ca. 1,1 GB pro Stand das dauert einen Moment).</p>
{% endif %}
</section>
{% endblock %}
+6
View File
@@ -9,6 +9,12 @@
<div class="card {{ 'alert' if stats.unseen else '' }}">
<span class="num">{{ stats.unseen }}</span><span class="lbl">ungelesene Änderungen</span>
</div>
{% if epirent_version %}
<div class="card">
<span class="num">{{ epirent_version.version }}</span>
<span class="lbl">EPIRent-Version<br>vom {{ epirent_version.released_at }}</span>
</div>
{% endif %}
<div class="card status">
{% if last_run %}
<span class="num {{ 'ok' if last_run.status == 'ok' else 'err' }}">