Files
docker-repo/auth-app/templates/images.html
T
duffyduck b273098b50 Compose: Variablen mit Default aufloesen (${VAR:-default})
Viele docker-compose-Dateien (z. B. Graylog) setzen Images per
${IMAGE:-default}. Bisher wurde alles mit "$" uebersprungen, sodass genau
diese Images nicht gefunden wurden. Jetzt werden ${VAR:-default} und
${VAR-default} mit ihrem Standardwert aufgeloest; nur Variablen ohne
Default (${VAR}, ${VAR:?...}) bleiben uebersprungen.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 10:59:01 +02:00

230 lines
12 KiB
HTML

{% extends "base.html" %}
{% block title %}Images - Docker Registry{% endblock %}
{# Wiederverwendbarer Zugangsdaten-Block (Benutzername/Kennwort ODER Access Token).
prefix unterscheidet die Felder von Einzel- und Bulk-Formular. #}
{% macro cred_fields(prefix) %}
<p style="margin-top: 0.75rem; font-size: 0.8rem; color: #6b7280;">
Nur bei privaten Quell-Images noetig &ndash; passend ankreuzen, was du hast:
</p>
<div class="form-group" style="margin-top: 0.4rem;">
<label style="display: flex; align-items: center; gap: 0.5rem; font-weight: 600;">
<input type="checkbox" id="{{ prefix }}use_creds" name="{{ prefix }}use_creds"
onchange="toggleCredMode('{{ prefix }}', 'creds')" style="width: auto;">
Benutzername / Kennwort
</label>
</div>
<div id="{{ prefix }}creds_box" style="display: none;">
<div class="form-row" style="margin-top: 0.5rem;">
<div class="form-group">
<label for="{{ prefix }}src_username">Benutzername</label>
<input type="text" id="{{ prefix }}src_username" name="{{ prefix }}src_username" autocomplete="off">
</div>
<div class="form-group">
<label for="{{ prefix }}src_password">Kennwort</label>
<input type="password" id="{{ prefix }}src_password" name="{{ prefix }}src_password" autocomplete="new-password">
</div>
</div>
</div>
<div class="form-group" style="margin-top: 0.6rem;">
<label style="display: flex; align-items: center; gap: 0.5rem; font-weight: 600;">
<input type="checkbox" id="{{ prefix }}use_token" name="{{ prefix }}use_token"
onchange="toggleCredMode('{{ prefix }}', 'token')" style="width: auto;">
Access Token
</label>
</div>
<div id="{{ prefix }}token_box" style="display: none;">
<div class="form-group" style="margin-top: 0.5rem;">
<label for="{{ prefix }}src_token">Access Token</label>
<input type="password" id="{{ prefix }}src_token" name="{{ prefix }}src_token" autocomplete="new-password"
placeholder="Bearer-Token der Quell-Registry">
</div>
</div>
{% endmacro %}
{% block content %}
<div class="card">
<h2>Image aus anderer Registry holen</h2>
<form method="post" action="{{ url_for('pull_image') }}"
onsubmit="showBusy('Image wird geholt &ndash; das kann je nach Groesse einige Minuten dauern. Bitte warten&hellip;');">
<div class="form-row">
<div class="form-group">
<label for="source">Quell-Image</label>
<input type="text" id="source" name="source"
placeholder="z. B. nginx:latest oder bitnami/redis:7" required>
</div>
<button type="submit" class="btn btn-primary">Holen</button>
</div>
{{ cred_fields('') }}
</form>
<div style="margin-top: 1.25rem; font-size: 0.9rem; color: #6b7280; line-height: 1.7;">
<p class="help-step"><strong>So funktioniert es</strong></p>
<p class="help-step">
Trage den Namen eines Images aus einer anderen Registry ein (z. B. von Docker Hub)
und klicke auf <strong>Holen</strong>. Das Image wird dann heruntergeladen und in
diese Registry kopiert (alle Architekturen).
</p>
<p class="help-step">Beispiele fuer das Quell-Image:</p>
<ul style="margin: 0.25rem 0 0.5rem 1.25rem;">
<li><code>nginx:latest</code> &ndash; offizielles Image von Docker Hub</li>
<li><code>bitnami/redis:7</code> &ndash; Image eines Docker-Hub-Benutzers</li>
<li><code>ghcr.io/owner/app:v1</code> &ndash; Image aus einer anderen Registry (z. B. GitHub)</li>
</ul>
<p class="help-step">
<strong>Public Images</strong> (der Normalfall, z. B. von Docker Hub) brauchen
keine Zugangsdaten &ndash; einfach nur den Namen eingeben und Holen.
</p>
<p class="help-step">
Nur bei <strong>privaten</strong> Quell-Images Zugangsdaten angeben &ndash; je nachdem,
was du hast:
</p>
<ul style="margin: 0.25rem 0 0.5rem 1.25rem;">
<li><strong>Benutzername / Kennwort</strong> &ndash; fuer Docker Hub und die meisten
Registries. Bei Docker Hub hier das <em>Access Token als Kennwort</em> eintragen.</li>
<li><strong>Access Token</strong> &ndash; fuer Registries, die einen direkten
Bearer-Token akzeptieren.</li>
</ul>
<p class="help-step">
Ist ein Image mit dem gleichen Digest bereits vorhanden, wird es automatisch
uebersprungen (kein Doppel-Upload &ndash; auch bei <code>latest</code>).
</p>
</div>
</div>
<div class="card">
<h2>Mehrere Images aus docker-compose holen</h2>
<form method="post" action="{{ url_for('pull_compose') }}" enctype="multipart/form-data"
onsubmit="showBusy('Images werden geholt &ndash; bei mehreren oder grossen Images kann das einige Minuten dauern. Bitte warten&hellip;');">
<div class="form-group">
<label for="compose">docker-compose einfuegen</label>
<textarea id="compose" name="compose" rows="10"
placeholder="services:&#10; web:&#10; image: nginx:latest&#10; cache:&#10; image: redis:7"></textarea>
</div>
<div class="form-group" style="margin-top: 0.75rem;">
<label for="compose_file">&hellip; oder eine Datei hochladen</label>
<input type="file" id="compose_file" name="compose_file"
accept=".yml,.yaml,.txt,text/yaml,text/plain">
</div>
{{ cred_fields('bulk_') }}
<div style="margin-top: 0.9rem;">
<button type="submit" class="btn btn-primary">Alle Images holen</button>
</div>
</form>
<div style="margin-top: 1.25rem; font-size: 0.9rem; color: #6b7280; line-height: 1.7;">
<p class="help-step"><strong>So funktioniert es</strong></p>
<p class="help-step">
Fuege den Inhalt einer <code>docker-compose.yml</code> ein oder lade die Datei hoch
und klicke auf <strong>Alle Images holen</strong>. Aus jedem Service wird der
<code>image:</code>-Eintrag gelesen und das Image in diese Registry kopiert.
</p>
<p class="help-step">
Variablen mit Standardwert (z. B. <code>${GRAYLOG_IMAGE:-graylog/graylog:7.1}</code>)
werden automatisch mit ihrem Standardwert aufgeloest. Eintraege mit Variablen
<em>ohne</em> Standardwert (z. B. <code>${TAG}</code>) oder die bereits auf diese
Registry zeigen, werden uebersprungen. Bereits vorhandene Images (gleicher Digest)
werden ebenfalls nicht erneut geladen.
</p>
<p class="help-step">
Etwaige Zugangsdaten gelten fuer <em>alle</em> Images der Compose-Datei.
</p>
</div>
</div>
<div class="card">
<h2>Gespeicherte Images</h2>
{% if repos %}
<table>
<thead>
<tr>
<th>Image</th>
<th>Tags</th>
</tr>
</thead>
<tbody>
{% for repo in repos %}
<tr>
<td><strong>{{ request.host }}/{{ repo.name }}</strong></td>
<td>
{% if repo.tags %}
{% for tag in repo.tags %}
<span class="tag tag-deletable">
{{ tag }}
<form method="post" action="{{ url_for('delete_image') }}" class="tag-delete-form"
onsubmit="return confirm('Image {{ repo.name }}:{{ tag }} wirklich loeschen?');">
<input type="hidden" name="name" value="{{ repo.name }}">
<input type="hidden" name="tag" value="{{ tag }}">
<button type="submit" title="Image loeschen" aria-label="Image loeschen">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="3 6 5 6 21 6"></polyline>
<path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path>
<line x1="10" y1="11" x2="10" y2="17"></line>
<line x1="14" y1="11" x2="14" y2="17"></line>
</svg>
</button>
</form>
</span>
{% endfor %}
{% else %}
<span style="color: #9ca3af;">keine Tags</span>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<p class="empty">Noch keine Images in der Registry vorhanden.</p>
{% endif %}
<div style="margin-top: 1.25rem; padding-top: 1rem; border-top: 1px solid #e5e7eb;">
<form method="post" action="{{ url_for('garbage_collect') }}"
onsubmit="if(!confirm('Garbage Collection jetzt ausfuehren? Loesche waehrenddessen keine Images und pushe nichts.')) return false; showBusy('Speicher wird aufgeraeumt. Bitte warten&hellip;');">
<button type="submit" class="btn btn-secondary">Speicher aufraeumen (Garbage Collection)</button>
</form>
<p style="margin-top: 0.6rem; font-size: 0.85rem; color: #6b7280; line-height: 1.6;">
Beim Loeschen eines Images verschwindet der Tag sofort, der belegte Speicherplatz wird
aber erst hierdurch freigegeben. Entfernt alle Daten, die von keinem Image mehr
referenziert werden. <strong>Wichtig:</strong> waehrend der Aufraeumung keine Images
hochladen, sonst koennen frisch hochgeladene Daten verloren gehen.
</p>
</div>
</div>
<div id="busy-overlay"
style="display: none; position: fixed; inset: 0; background: rgba(15,23,42,0.6);
z-index: 100; align-items: center; justify-content: center;">
<div style="background: #fff; padding: 1.5rem 2rem; border-radius: 8px; max-width: 420px;
text-align: center; box-shadow: 0 10px 25px rgba(0,0,0,0.2);">
<div class="spinner" style="width: 32px; height: 32px; margin: 0 auto 1rem;
border: 3px solid #e5e7eb; border-top-color: #2563eb; border-radius: 50%;
animation: spin 0.8s linear infinite;"></div>
<p id="busy-text" style="font-size: 0.95rem; color: #374151; line-height: 1.5;">Bitte warten&hellip;</p>
</div>
</div>
<style>@keyframes spin { to { transform: rotate(360deg); } }</style>
<script>
// Vollbild-Hinweis fuer langlaufende Aktionen (Pull / Garbage Collection).
function showBusy(htmlText) {
document.getElementById('busy-text').innerHTML = htmlText;
document.getElementById('busy-overlay').style.display = 'flex';
return true;
}
// Benutzername/Kennwort und Access Token schliessen sich gegenseitig aus.
function toggleCredMode(prefix, mode) {
var creds = document.getElementById(prefix + 'use_creds');
var token = document.getElementById(prefix + 'use_token');
if (mode === 'creds' && creds.checked) { token.checked = false; }
if (mode === 'token' && token.checked) { creds.checked = false; }
document.getElementById(prefix + 'creds_box').style.display = creds.checked ? 'block' : 'none';
document.getElementById(prefix + 'token_box').style.display = token.checked ? 'block' : 'none';
}
</script>
{% endblock %}