Initial commit

This commit is contained in:
Kristof committed 2026-10-14 12:29:29 +02:00
commit 536ff43ce4
158 files changed
+30355

No files matched your search

+159
View File
@@ -0,0 +1,159 @@
{% extends "base.html" %}
{% block title %}Mails verplaatsen{% endblock %}
{% block page_title %}Mails verplaatsen{% endblock %}
{% block main_class %}medium{% endblock %}
{% block loader_text %}Bezig met Google...{% endblock %}
{% block extra_css %}
<style>
.stappen { display: grid; gap: 14px; }
.rij { display: flex; flex-wrap: wrap; gap: 10px; align-items: end; }
.rij .field { flex: 1 1 260px; margin: 0; }
.modus { display: flex; flex-wrap: wrap; gap: 10px; }
.modus label { flex: 1 1 240px; display: flex; gap: 10px; align-items: flex-start; padding: 12px 14px; margin: 0;
border: 1px solid var(--border); border-radius: var(--radius); cursor: pointer; font-weight: 400; }
.modus label:has(input:checked) { border-color: var(--primary-color); background: var(--primary-light); }
.modus input { width: auto; margin-top: 3px; }
.modus strong { display: block; }
.samenvatting { display: flex; flex-wrap: wrap; align-items: center; gap: 12px; justify-content: space-between;
padding: 14px 16px; border-radius: var(--radius); background: var(--surface-muted); margin-top: 16px; }
.samenvatting .pijl { font-size: 1.3em; color: var(--text-muted, #64748b); }
.voortgang { height: 10px; border-radius: 999px; background: var(--surface-muted); overflow: hidden; margin: 10px 0 6px; }
.voortgang div { height: 100%; width: 0; background: var(--primary-color); transition: width .4s; }
.tellers { display: flex; flex-wrap: wrap; gap: 14px; font-size: .92em; }
td.nowrap { white-space: nowrap; }
</style>
{% endblock %}
{% block content %}
<div class="card">
<h2 class="card-title">🏷️ Berichten met een label naar een andere mailbox</h2>
<p class="field-hint" style="margin-top:-6px">Kies de mailbox en het label. PIVOT kopieert elk bericht met dat label (met bijlagen,
originele datum, gelezen/ongelezen en ster) naar de doelmailbox, onder een label met dezelfde naam.</p>
<form method="GET" action="{{ url_for('gmail_labels.overzicht') }}" class="stappen" onsubmit="showLoader()">
<div class="rij">
<div class="field">
<label for="bron">1. Bronmailbox</label>
<input type="email" id="bron" name="bron" required value="{{ bron.email if bron else bron_in }}" placeholder="bv. secretariaat@school.be">
</div>
<button type="submit" class="btn-secondary">🔍 Labels ophalen</button>
</div>
{% if bron %}
<div class="muted small" style="margin-top:-6px">📬 {{ bron.naam or bron.email }} · {{ bron.email }}{% if bron.ou %} · {{ bron.ou }}{% endif %}</div>
{% if labels %}
<div class="field" style="margin:0">
<label for="label">2. Label</label>
<select id="label" name="label" required>
<option value="">— kies een label —</option>
{% for l in labels %}
<option value="{{ l.id }}" {{ 'selected' if gekozen and gekozen.id == l.id }}>{{ l.naam }}{% if l.aantal is not none %} ({{ l.aantal }}){% endif %}</option>
{% endfor %}
</select>
</div>
<div class="field" style="margin:0">
<label for="doel">3. Doelmailbox</label>
<input type="email" id="doel" name="doel" required value="{{ doel.email if doel else doel_in }}" placeholder="bv. directie@school.be">
</div>
<div><button type="submit" class="btn-secondary">Controleren</button></div>
{% elif not fout %}
<p class="muted">Deze mailbox heeft geen eigen labels.</p>
{% endif %}
{% endif %}
</form>
{% if fout %}<div class="alert alert-danger" style="margin-top:14px">{{ fout }}</div>{% endif %}
{% if bron and doel and gekozen %}
<form method="POST" action="{{ url_for('gmail_labels.start') }}" style="margin-top:18px"
onsubmit="const v = this.modus.value === 'verplaatsen'; return confirm({{ (gekozen.aantal|string if gekozen.aantal is not none else 'Alle') |tojson|forceescape }} + ' bericht(en) met label ' + {{ gekozen.naam|tojson|forceescape }} + (v ? ' VERPLAATSEN' : ' kopiëren') + ' van ' + {{ bron.email|tojson|forceescape }} + ' naar ' + {{ doel.email|tojson|forceescape }} + '?' + (v ? '\n\nIn de bronmailbox gaan ze naar de prullenbak (30 dagen terug te halen).' : ''));">
<input type="hidden" name="bron" value="{{ bron.email }}">
<input type="hidden" name="doel" value="{{ doel.email }}">
<input type="hidden" name="label" value="{{ gekozen.id }}">
<label style="margin-bottom:8px">4. Wat moet er gebeuren?</label>
<div class="modus">
<label><input type="radio" name="modus" value="kopieren" checked>
<span><strong>📋 Kopiëren</strong><span class="muted small">De berichten blijven ook in de bronmailbox staan.</span></span></label>
<label><input type="radio" name="modus" value="verplaatsen">
<span><strong>📦 Verplaatsen</strong><span class="muted small">Na het kopiëren gaan ze in de bronmailbox naar de prullenbak.</span></span></label>
</div>
<div class="samenvatting">
<span><strong>{{ gekozen.aantal if gekozen.aantal is not none else '?' }}</strong> bericht(en) met label <strong>{{ gekozen.naam }}</strong></span>
<span>{{ bron.email }} <span class="pijl">→</span> {{ doel.email }}</span>
<button type="submit" class="btn-success" {{ 'disabled' if status.bezig }}>▶ Starten</button>
</div>
</form>
{% endif %}
</div>
{% if status.bezig or status.resultaat or status.fout %}
<div class="card" id="voortgangKaart">
<h3 class="card-title">⏳ Opdracht</h3>
{% if status.opdracht %}<p class="muted small" id="opdracht">Label <strong>{{ status.opdracht.label }}</strong>:
{{ status.opdracht.bron }} → {{ status.opdracht.doel }} ({{ 'verplaatsen' if status.opdracht.modus == 'verplaatsen' else 'kopiëren' }})</p>{% endif %}
<div class="voortgang"><div id="balk"></div></div>
<div class="tellers">
<span><b id="tVoortgang">{{ status.voortgang }}</b> / <b id="tTotaal">{{ status.totaal }}</b></span>
<span>✅ <b id="tGelukt">{{ status.gelukt }}</b> gelukt</span>
<span>⏭️ <b id="tOver">{{ status.overgeslagen }}</b> stonden er al</span>
<span>⚠️ <b id="tFout">{{ status.fouten }}</b> mislukt</span>
</div>
<div class="alert alert-success" id="resultaat" style="margin-top:12px" {{ '' if status.resultaat and not status.bezig else 'hidden' }}>{{ status.resultaat or '' }}</div>
<div class="alert alert-danger" id="foutmelding" style="margin-top:12px" {{ '' if status.fout else 'hidden' }}>{{ status.fout or '' }}</div>
<ul class="muted small" id="foutvoorbeelden">{% for f in status.foutvoorbeelden %}<li>{{ f }}</li>{% endfor %}</ul>
</div>
{% endif %}
{% if geschiedenis %}
<div class="card">
<h3 class="card-title">🕑 Vorige opdrachten</h3>
<div class="table-wrap">
<table>
<thead><tr><th>Gestart</th><th>Door</th><th>Label</th><th>Van → naar</th><th>Soort</th><th class="text-right">Gelukt</th><th class="text-right">Mislukt</th></tr></thead>
<tbody>
{% for h in geschiedenis %}
<tr title="{{ h.Melding or '' }}">
<td class="nowrap">{{ h.Gestart.strftime('%d-%m-%Y %H:%M') }}{% if not h.Klaar %} <span class="badge badge-warning">bezig</span>{% endif %}</td>
<td>{{ h.Door or '' }}</td>
<td><strong>{{ h.Label }}</strong></td>
<td class="small">{{ h.Bron }} → {{ h.Doel }}</td>
<td>{{ 'Verplaatst' if h.Modus == 'verplaatsen' else 'Gekopieerd' }}</td>
<td class="text-right">{{ h.Gelukt if h.Gelukt is not none else '' }}{% if h.Overgeslagen %} <span class="muted small">(+{{ h.Overgeslagen }})</span>{% endif %}</td>
<td class="text-right">{{ h.Fouten or '' }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% endif %}
<p class="field-hint">Eenmalig nodig in de Google Admin-console (Beveiliging › API-beheer › Domeinbrede delegatie, bij het
serviceaccount van PIVOT): het bereik <code>{{ scope }}</code>. Een onderbroken opdracht kan je gewoon opnieuw starten:
berichten die al gekopieerd werden, worden overgeslagen.</p>
{% endblock %}
{% block scripts %}
<script>
(function () {
const kaart = document.getElementById('voortgangKaart');
if (!kaart) return;
const zet = (id, w) => document.getElementById(id).textContent = w;
function kijk() {
fetch({{ url_for('gmail_labels.status')|tojson }}, { credentials: 'same-origin' }).then(r => r.json()).then(s => {
zet('tVoortgang', s.voortgang); zet('tTotaal', s.totaal); zet('tGelukt', s.gelukt);
zet('tOver', s.overgeslagen); zet('tFout', s.fouten);
document.getElementById('balk').style.width = (s.totaal ? Math.round(100 * s.voortgang / s.totaal) : (s.bezig ? 3 : 100)) + '%';
const res = document.getElementById('resultaat'), fout = document.getElementById('foutmelding');
res.hidden = !(s.resultaat && !s.bezig); res.textContent = s.resultaat || '';
fout.hidden = !s.fout; fout.textContent = s.fout || '';
document.getElementById('foutvoorbeelden').innerHTML = (s.foutvoorbeelden || []).map(f => `<li>${f.replace(/</g, '&lt;')}</li>`).join('');
if (s.bezig) setTimeout(kijk, 2000);
}).catch(() => setTimeout(kijk, 5000));
}
kijk();
})();
</script>
{% endblock %}