Upload via GUI (346 Dateien)
This commit is contained in:
@@ -313,6 +313,12 @@ require __DIR__ . '/_header.php';
|
||||
on.addEventListener('change', sync); sync();
|
||||
}
|
||||
toggler('imap_enabled', 'imap-fields'); toggler('smtp_enabled', 'smtp-fields');
|
||||
// Verschlüsselung gewählt: Port passend setzen (nur wenn dort noch ein Standardport steht, eigene Ports bleiben)
|
||||
var enc = document.getElementById('smtp_encryption'), prt = document.getElementById('smtp_port');
|
||||
if (enc && prt) enc.addEventListener('change', function () {
|
||||
var std = { tls: '587', ssl: '465', none: '25' };
|
||||
if (['25', '465', '587', ''].indexOf(String(prt.value).trim()) !== -1) prt.value = std[enc.value] || prt.value;
|
||||
});
|
||||
|
||||
var af = document.getElementById('after_fetch'), ar = document.getElementById('archive-row');
|
||||
function syncAf() { ar.style.display = af.value === 'archive' ? '' : 'none'; }
|
||||
|
||||
@@ -57,6 +57,36 @@ require __DIR__ . '/_header.php';
|
||||
<?php if (count($langs) < 2): ?><p class="muted" style="margin-top:8px">Noch kein Sprachpaket aktiv. Installieren Sie zum Beispiel „English“ unter <a href="/staff/plugins.php">Plugins</a>; dann erscheinen die Flaggen.</p><?php endif; ?>
|
||||
</div>
|
||||
|
||||
<?php
|
||||
$pluginLangs = i18n_plugin_language_overview();
|
||||
$otherCodes = array_values(array_filter(array_keys($langs), fn($c) => $c !== 'de'));
|
||||
$activePlugins = [];
|
||||
try { $activePlugins = $pdo->query("SELECT slug FROM plugins WHERE is_active = 1 ORDER BY slug")->fetchAll(PDO::FETCH_COLUMN); } catch (Throwable $e) { }
|
||||
$activePlugins = array_values(array_filter($activePlugins, fn($sl) => !isset($langs) || !in_array($sl, array_column($langs, 'slug'), true)));
|
||||
?>
|
||||
<div class="card" style="max-width:860px;margin-bottom:16px">
|
||||
<h3>Übersetzungen in den Plugins</h3>
|
||||
<p class="muted" style="font-size:.88rem">Jedes Plugin kann seine Texte selbst übersetzen: Es legt dazu im Plugin-Ordner <code>lang/<Sprachcode>.json</code> ab (zum Beispiel <code>lang/en.json</code>). Wird eine Sprache gewählt, nutzt DeskHub diese Dateien aller aktiven Plugins zusätzlich zum Sprachpaket; steht ein Text in beiden, gilt der aus dem Sprachpaket. Eine neue Sprache braucht deshalb nur ein Sprachpaket und je Plugin eine Datei.</p>
|
||||
<?php if (!$otherCodes): ?>
|
||||
<p class="muted">Noch kein Sprachpaket aktiv. Installieren Sie zum Beispiel „English“ unter <a href="/staff/plugins.php">Plugins</a>.</p>
|
||||
<?php elseif (!$activePlugins): ?>
|
||||
<p class="muted">Es ist kein Plugin aktiv.</p>
|
||||
<?php else: ?>
|
||||
<div style="overflow-x:auto"><table style="width:100%">
|
||||
<thead><tr><th>Plugin</th><?php foreach ($otherCodes as $c): ?><th><?= e($langs[$c]['name']) ?> (<?= e($c) ?>)</th><?php endforeach; ?></tr></thead>
|
||||
<tbody>
|
||||
<?php foreach ($activePlugins as $sl): ?>
|
||||
<tr><td><?= e((string) $sl) ?></td>
|
||||
<?php foreach ($otherCodes as $c): $n = $pluginLangs[$sl][$c] ?? null; ?>
|
||||
<td><?php if ($n === null): ?><span class="muted">keine Datei (Texte aus dem Sprachpaket oder deutsch)</span><?php else: ?>✓ <?= (int) $n ?> Texte<?php endif; ?></td>
|
||||
<?php endforeach; ?>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table></div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<form method="post" class="card" style="max-width:860px;margin-bottom:16px"><?= csrf_field() ?><input type="hidden" name="action" value="save">
|
||||
<h3>Einstellungen</h3>
|
||||
<label for="default_language">Standardsprache (für alle, die noch keine Flagge gewählt haben)</label>
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../includes/bootstrap.php';
|
||||
$admin = require_admin();
|
||||
$pdo = get_db();
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
verify_csrf();
|
||||
$action = (string) ($_POST['action'] ?? '');
|
||||
$id = (int) ($_POST['id'] ?? 0);
|
||||
try {
|
||||
if ($action === 'save') {
|
||||
set_setting('mail_queue_enabled', !empty($_POST['mail_queue_enabled']) ? '1' : '0');
|
||||
audit_log($admin['id'], 'Mail-Warteschlange ' . (!empty($_POST['mail_queue_enabled']) ? 'eingeschaltet' : 'ausgeschaltet'));
|
||||
flash('success', 'Gespeichert.');
|
||||
} elseif ($action === 'retry_now') {
|
||||
// Eine Mail (id) oder alle wartenden (id = 0) jetzt versuchen
|
||||
$now = db_now();
|
||||
if ($id > 0) $pdo->prepare("UPDATE mail_queue SET status = 'pending', next_attempt_at = ? WHERE id = ? AND status IN ('pending','failed')")->execute([$now, $id]);
|
||||
else $pdo->prepare("UPDATE mail_queue SET next_attempt_at = ? WHERE status = 'pending'")->execute([$now]);
|
||||
$r = mail_queue_run($pdo, $id > 0 ? 1 : 50);
|
||||
flash($r['sent'] > 0 || $r['failed'] === 0 ? 'success' : 'error', $r['sent'] . ' Mail(s) gesendet' . ($r['failed'] > 0 ? ', ' . $r['failed'] . ' weiterhin fehlgeschlagen (Fehler steht in der Liste).' : '.'));
|
||||
} elseif ($action === 'requeue_failed') {
|
||||
$pdo->prepare("UPDATE mail_queue SET status = 'pending', attempts = 0, next_attempt_at = ? WHERE status = 'failed'")->execute([db_now()]);
|
||||
flash('success', 'Aufgegebene Mails werden erneut versucht.');
|
||||
} elseif ($action === 'delete' && $id > 0) {
|
||||
$pdo->prepare('DELETE FROM mail_queue WHERE id = ?')->execute([$id]);
|
||||
audit_log($admin['id'], "Mail #{$id} aus der Warteschlange gelöscht");
|
||||
flash('success', 'Mail gelöscht.');
|
||||
} elseif ($action === 'purge') {
|
||||
$n = $pdo->exec("DELETE FROM mail_queue WHERE status IN ('sent','failed')");
|
||||
audit_log($admin['id'], 'Erledigte und aufgegebene Mails aus der Warteschlange gelöscht');
|
||||
flash('success', (int) $n . ' Eintrag/Einträge gelöscht.');
|
||||
}
|
||||
} catch (Throwable $e) {
|
||||
flash('error', 'Aktion nicht möglich: ' . $e->getMessage());
|
||||
}
|
||||
redirect('/staff/mail_queue.php');
|
||||
}
|
||||
|
||||
$counts = mail_queue_counts($pdo);
|
||||
$rows = [];
|
||||
try {
|
||||
$rows = $pdo->query("SELECT id, to_email, subject, status, attempts, next_attempt_at, last_error, created_at, sent_at FROM mail_queue ORDER BY (CASE status WHEN 'pending' THEN 0 WHEN 'failed' THEN 1 ELSE 2 END), id DESC LIMIT 200")->fetchAll();
|
||||
} catch (Throwable $e) {
|
||||
$rows = [];
|
||||
}
|
||||
$statusLabels = ['pending' => ['Wartet auf nächsten Versuch', 'warning'], 'failed' => ['Aufgegeben', 'danger'], 'sent' => ['Nachträglich gesendet', 'success']];
|
||||
$pageTitle = 'Mail-Warteschlange';
|
||||
$activePage = 'mail_queue';
|
||||
require __DIR__ . '/_header.php';
|
||||
?>
|
||||
<h1>Mail-Warteschlange</h1>
|
||||
<p class="muted" style="max-width:860px">Scheitert der Mailversand (zum Beispiel weil der SMTP-Server nicht erreichbar ist), merkt sich DeskHub die Mail und versucht es alle paar Minuten erneut: nach 5, 10, 20, 40 Minuten und dann in größeren Abständen, höchstens 10-mal und höchstens 48 Stunden lang. Danach gilt sie als aufgegeben und bleibt hier zum manuellen Nachsenden. Test-Mails kommen nicht in die Warteschlange.</p>
|
||||
|
||||
<div style="display:flex;gap:12px;flex-wrap:wrap;margin-bottom:16px">
|
||||
<div class="card" style="margin:0;min-width:160px"><div class="muted" style="font-size:.8rem">Wartend</div><div style="font-size:1.6rem;font-weight:800"><?= (int) $counts['pending'] ?></div></div>
|
||||
<div class="card" style="margin:0;min-width:160px"><div class="muted" style="font-size:.8rem">Aufgegeben</div><div style="font-size:1.6rem;font-weight:800"><?= (int) $counts['failed'] ?></div></div>
|
||||
<div class="card" style="margin:0;min-width:160px"><div class="muted" style="font-size:.8rem">Nachträglich gesendet</div><div style="font-size:1.6rem;font-weight:800"><?= (int) $counts['sent'] ?></div></div>
|
||||
</div>
|
||||
|
||||
<form method="post" class="card" style="max-width:860px;margin-bottom:16px"><?= csrf_field() ?><input type="hidden" name="action" value="save">
|
||||
<label style="display:flex;gap:8px;align-items:flex-start;font-weight:400"><input type="checkbox" name="mail_queue_enabled" value="1" style="width:auto;margin-top:3px" <?= mail_queue_enabled() ? 'checked' : '' ?>>
|
||||
<span><strong>Fehlgeschlagene Mails später erneut senden</strong> (empfohlen). Ausgeschaltet gehen Mails bei einer Störung verloren.</span></label>
|
||||
<div class="btn-row"><button class="btn" type="submit">Speichern</button></div>
|
||||
</form>
|
||||
|
||||
<div class="card">
|
||||
<div style="display:flex;gap:8px;flex-wrap:wrap;align-items:center;margin-bottom:10px">
|
||||
<h3 style="margin:0;flex:1">Mails in der Warteschlange</h3>
|
||||
<form method="post"><?= csrf_field() ?><input type="hidden" name="action" value="retry_now"><input type="hidden" name="id" value="0"><button class="btn secondary" type="submit" <?= $counts['pending'] ? '' : 'disabled' ?>>Alle wartenden jetzt senden</button></form>
|
||||
<form method="post"><?= csrf_field() ?><input type="hidden" name="action" value="requeue_failed"><button class="btn secondary" type="submit" <?= $counts['failed'] ? '' : 'disabled' ?>>Aufgegebene erneut versuchen</button></form>
|
||||
<form method="post" data-confirm="Erledigte und aufgegebene Einträge löschen?" onsubmit="return confirm(this.dataset.confirm)"><?= csrf_field() ?><input type="hidden" name="action" value="purge"><button class="btn secondary" type="submit" <?= ($counts['sent'] + $counts['failed']) ? '' : 'disabled' ?>>Erledigte und aufgegebene löschen</button></form>
|
||||
</div>
|
||||
<?php if (!$rows): ?>
|
||||
<p class="muted">Die Warteschlange ist leer. Alle Mails sind rausgegangen.</p>
|
||||
<?php else: ?>
|
||||
<div style="overflow-x:auto"><table style="width:100%">
|
||||
<thead><tr><th>Status</th><th>Empfänger</th><th>Betreff</th><th>Versuche</th><th>Nächster Versuch</th><th>Letzter Fehler</th><th></th></tr></thead>
|
||||
<tbody>
|
||||
<?php foreach ($rows as $r): [$label, $cls] = $statusLabels[$r['status']] ?? [$r['status'], 'muted']; ?>
|
||||
<tr>
|
||||
<td><span class="badge <?= e($cls) ?>"><?= e($label) ?></span><div class="muted" style="font-size:.72rem">seit <?= e(format_dt($r['created_at'])) ?></div></td>
|
||||
<td><?= e($r['to_email']) ?></td>
|
||||
<td><?= e($r['subject']) ?></td>
|
||||
<td><?= (int) $r['attempts'] ?></td>
|
||||
<td><?= $r['status'] === 'pending' ? e(format_dt($r['next_attempt_at'])) : ($r['status'] === 'sent' ? e(format_dt((string) $r['sent_at'])) : '–') ?></td>
|
||||
<td style="max-width:280px;font-size:.8rem"><?= e((string) $r['last_error']) ?></td>
|
||||
<td style="white-space:nowrap">
|
||||
<?php if ($r['status'] !== 'sent'): ?>
|
||||
<form method="post" style="display:inline"><?= csrf_field() ?><input type="hidden" name="action" value="retry_now"><input type="hidden" name="id" value="<?= (int) $r['id'] ?>"><button class="btn secondary" style="padding:3px 10px;font-size:.8rem" type="submit">Jetzt senden</button></form>
|
||||
<?php endif; ?>
|
||||
<form method="post" style="display:inline" data-confirm="Diese Mail endgültig löschen? Sie wird dann nicht mehr gesendet." onsubmit="return confirm(this.dataset.confirm)"><?= csrf_field() ?><input type="hidden" name="action" value="delete"><input type="hidden" name="id" value="<?= (int) $r['id'] ?>"><button class="btn secondary" style="padding:3px 10px;font-size:.8rem" type="submit">Löschen</button></form>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table></div>
|
||||
<?php if (count($rows) >= 200): ?><p class="muted" style="font-size:.8rem">Es werden die ersten 200 Einträge angezeigt.</p><?php endif; ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php require __DIR__ . '/_footer.php'; ?>
|
||||
Reference in New Issue
Block a user