E-Mail-Retry, Voucher-Resend, Tageslimit, Docker-Politur
- Mailer::send mit Retry (2 Versuche); SMTP-Test & Templates waren bereits da - admin/vouchers.php: Code per E-Mail (erneut) versenden (ajax_resend) - Tageslimit Voucher pro Nicht-Admin-Benutzer (Setting + Durchsetzung in index) - Docker: HEALTHCHECK (health.php) + curl; GHCR-Publish-Workflow - Setting user_daily_voucher_limit + enforce in integrations.php
This commit is contained in:
parent
2e0f36d6c1
commit
997cda01a8
14 changed files with 350 additions and 8 deletions
|
|
@ -24,6 +24,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['save'])) {
|
|||
$error = __('error_csrf');
|
||||
} else {
|
||||
$db->setSetting('enforce_2fa_admins', isset($_POST['enforce_2fa_admins']) ? '1' : '0');
|
||||
$db->setSetting('user_daily_voucher_limit', max(0, (int)($_POST['user_daily_voucher_limit'] ?? 0)));
|
||||
$db->setSetting('trusted_proxy', trim($_POST['trusted_proxy'] ?? ''));
|
||||
$db->setSetting('webhook_enabled', isset($_POST['webhook_enabled']) ? '1' : '0');
|
||||
$db->setSetting('webhook_url', trim($_POST['webhook_url'] ?? ''));
|
||||
|
|
@ -41,6 +42,7 @@ if (isset($_GET['test_webhook']) && isset($_GET['token']) && $auth->validateCsrf
|
|||
}
|
||||
|
||||
$enforce2fa = $db->getSetting('enforce_2fa_admins', '0') === '1';
|
||||
$dailyLimit = (int)$db->getSetting('user_daily_voucher_limit', 0);
|
||||
$trustedProxy = $db->getSetting('trusted_proxy', '');
|
||||
$webhookEnabled = $db->getSetting('webhook_enabled', '0') === '1';
|
||||
$webhookUrl = $db->getSetting('webhook_url', '');
|
||||
|
|
@ -86,6 +88,8 @@ label { display:block; font-size:14px; color:var(--text-secondary); margin:14px
|
|||
<h2>Sicherheitsrichtlinie</h2>
|
||||
<p class="muted">Erzwingt Zwei-Faktor-Authentifizierung für alle Administrator-Konten (lokale Accounts). Admins ohne 2FA werden bei der nächsten Aktion zur Einrichtung geleitet.</p>
|
||||
<label class="chk"><input type="checkbox" name="enforce_2fa_admins" <?= $enforce2fa ? 'checked' : '' ?>> 2FA für Administratoren verpflichtend</label>
|
||||
<label>Tageslimit Voucher pro Nicht-Admin-Benutzer (0 = unbegrenzt)</label>
|
||||
<input class="input" type="number" min="0" name="user_daily_voucher_limit" value="<?= $dailyLimit ?>" style="max-width:200px;">
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
|
|
|
|||
|
|
@ -96,6 +96,25 @@ if (isset($_POST['ajax_delete']) && isset($_POST['voucher_id']) && isset($_POST[
|
|||
exit;
|
||||
}
|
||||
|
||||
// Voucher-Code per E-Mail (erneut) versenden
|
||||
if (isset($_POST['ajax_resend']) && isset($_POST['voucher_id']) && isset($_POST['site_id'])) {
|
||||
header('Content-Type: application/json');
|
||||
if (!$auth->validateCsrfToken($_POST['csrf_token']??'')) { echo json_encode(['success'=>false,'message'=>__('error_csrf')]); exit; }
|
||||
require_once __DIR__ . '/../includes/Mailer.php';
|
||||
$email = trim($_POST['email'] ?? '');
|
||||
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) { echo json_encode(['success'=>false,'message'=>'Ungültige E-Mail-Adresse']); exit; }
|
||||
$siteId = (int)$_POST['site_id'];
|
||||
$site = $db->fetchOne("SELECT * FROM sites WHERE id=?", [$siteId]);
|
||||
$v = $db->fetchOne("SELECT * FROM vouchers WHERE unifi_voucher_id=? AND site_id=?", [$_POST['voucher_id'], $siteId]);
|
||||
if (!$site || !$v) { echo json_encode(['success'=>false,'message'=>'Voucher nicht gefunden']); exit; }
|
||||
$mailer = new Mailer();
|
||||
$code = strpos($v['voucher_code'], '-') !== false ? $v['voucher_code'] : implode('-', str_split($v['voucher_code'], 5));
|
||||
$ok = $mailer->sendVoucherEmail($email, $code, $site['name'], (int)$v['max_uses']);
|
||||
$auth->writeAuditLog($_SESSION['user_id'], 'voucher_resend', 'voucher', $v['id'], "Code an $email gesendet");
|
||||
echo json_encode(['success'=>$ok, 'message'=>$ok ? 'E-Mail versendet.' : 'Versand fehlgeschlagen.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$sites = $db->fetchAll("SELECT * FROM sites WHERE is_active=1 ORDER BY name");
|
||||
$siteStats = [];
|
||||
foreach ($sites as $site) {
|
||||
|
|
@ -383,7 +402,10 @@ function renderVouchers() {
|
|||
<td>${statusBadge}</td>
|
||||
<td><div class="usage-info"><span>${v.used}/${v.quota>0?v.quota:'∞'}</span>${v.quota>0?`<div class="usage-bar"><div class="usage-bar-fill" style="width:${usagePct}%"></div></div>`:''}</div></td>
|
||||
<td>${remaining?`<span style="color:var(--success)"><i class="fas fa-clock"></i> ${remaining}</span><br>`:''}<small style="color:var(--text-muted)">${v.duration} Min.</small></td>
|
||||
<td><button onclick="deleteVoucher('${v._id}')" class="btn btn-danger btn-sm" title="<?= __('btn_delete') ?>"><i class="fas fa-trash"></i></button></td>
|
||||
<td style="white-space:nowrap;">
|
||||
<button onclick="resendVoucher('${v._id}','${escapeHtml(v.formatted_code||'')}')" class="btn btn-secondary btn-sm" title="Per E-Mail senden"><i class="fas fa-envelope"></i></button>
|
||||
<button onclick="deleteVoucher('${v._id}')" class="btn btn-danger btn-sm" title="<?= __('btn_delete') ?>"><i class="fas fa-trash"></i></button>
|
||||
</td>
|
||||
</tr>`;
|
||||
});
|
||||
|
||||
|
|
@ -407,6 +429,20 @@ function renderVouchers() {
|
|||
|
||||
function escapeHtml(t) { const d=document.createElement('div'); d.textContent=t; return d.innerHTML; }
|
||||
|
||||
async function resendVoucher(voucherId, code) {
|
||||
const email = prompt('Code ' + code + ' senden an (E-Mail):');
|
||||
if (!email) return;
|
||||
const fd = new FormData();
|
||||
fd.append('ajax_resend','1'); fd.append('voucher_id',voucherId);
|
||||
fd.append('site_id', currentSiteId); fd.append('email', email);
|
||||
fd.append('csrf_token', csrfToken);
|
||||
try {
|
||||
const r = await fetch('vouchers.php', {method:'POST', body:fd});
|
||||
const d = await r.json();
|
||||
(window.showToast ? showToast(d.message, d.success?'success':'error') : alert(d.message));
|
||||
} catch(e){ alert('Fehler: '+e.message); }
|
||||
}
|
||||
|
||||
async function deleteVoucher(voucherId) {
|
||||
if (!confirm('Voucher wirklich löschen?')) return;
|
||||
const row = document.getElementById(`voucher-${voucherId}`);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue