Self-Hosting, Branding, i18n, Barrierefreiheit und Werkzeuge #2

Merged
friloo merged 8 commits from feature/self-hosted-assets-branding into main 2026-09-23 06:54:40 +00:00
21 changed files with 716 additions and 632 deletions
Showing only changes of commit 7f1d93debd - Show all commits

View file

@ -115,17 +115,17 @@ $adminBase = '';
<p class="muted"><?= __('api_none') ?></p> <p class="muted"><?= __('api_none') ?></p>
<?php else: ?> <?php else: ?>
<div class="table-container"> <div class="table-container">
<table> <table class="table-stack">
<tr><th><?= __('label_name') ?></th><th><?= __('api_col_prefix') ?></th><th><?= __('api_col_scope') ?></th><th><?= __('api_col_limit') ?></th><th><?= __('label_status') ?></th><th><?= __('api_col_last_used') ?></th><th><?= __('api_col_created_by') ?></th><th></th></tr> <tr><th><?= __('label_name') ?></th><th><?= __('api_col_prefix') ?></th><th><?= __('api_col_scope') ?></th><th><?= __('api_col_limit') ?></th><th><?= __('label_status') ?></th><th><?= __('api_col_last_used') ?></th><th><?= __('api_col_created_by') ?></th><th></th></tr>
<?php foreach ($keys as $k): ?> <?php foreach ($keys as $k): ?>
<tr> <tr>
<td><?= htmlspecialchars($k['name']) ?></td> <td data-label="<?= __('label_name') ?>"><?= htmlspecialchars($k['name']) ?></td>
<td><code>uvt_<?= htmlspecialchars($k['key_prefix']) ?>…</code></td> <td data-label="<?= __('api_col_prefix') ?>"><code>uvt_<?= htmlspecialchars($k['key_prefix']) ?>…</code></td>
<td><?= ($k['scope'] ?? 'write') === 'read' ? __('api_scope_read_short') : __('api_scope_write_short') ?></td> <td data-label="<?= __('api_col_scope') ?>"><?= ($k['scope'] ?? 'write') === 'read' ? __('api_scope_read_short') : __('api_scope_write_short') ?></td>
<td><?= (int)($k['rate_limit'] ?? 0) === 0 ? '∞' : (int)$k['rate_limit'] . '/min' ?></td> <td data-label="<?= __('api_col_limit') ?>"><?= (int)($k['rate_limit'] ?? 0) === 0 ? '∞' : (int)$k['rate_limit'] . '/min' ?></td>
<td><span class="badge <?= $k['is_active'] ? 'b-on' : 'b-off' ?>"><?= $k['is_active'] ? __('api_state_active') : __('api_state_blocked') ?></span></td> <td data-label="<?= __('label_status') ?>"><span class="badge <?= $k['is_active'] ? 'b-on' : 'b-off' ?>"><?= $k['is_active'] ? __('api_state_active') : __('api_state_blocked') ?></span></td>
<td class="muted"><?= $k['last_used_at'] ? date('d.m.Y H:i', strtotime($k['last_used_at'])) : '' ?></td> <td class="muted" data-label="<?= __('api_col_last_used') ?>"><?= $k['last_used_at'] ? date('d.m.Y H:i', strtotime($k['last_used_at'])) : '' ?></td>
<td class="muted"><?= htmlspecialchars($k['creator'] ?? '') ?></td> <td class="muted" data-label="<?= __('api_col_created_by') ?>"><?= htmlspecialchars($k['creator'] ?? '') ?></td>
<td style="text-align:right;white-space:nowrap;"> <td style="text-align:right;white-space:nowrap;">
<a class="a-link" href="?toggle=<?= (int)$k['id'] ?>&token=<?= urlencode($csrf) ?>"><?= $k['is_active'] ? __('api_action_block') : __('api_action_unblock') ?></a> <a class="a-link" href="?toggle=<?= (int)$k['id'] ?>&token=<?= urlencode($csrf) ?>"><?= $k['is_active'] ? __('api_action_block') : __('api_action_unblock') ?></a>
<a class="a-link" style="color:var(--danger);" href="?delete=<?= (int)$k['id'] ?>&token=<?= urlencode($csrf) ?>" onclick="return confirm('<?= __('api_delete_confirm') ?>');"><?= __('btn_delete') ?></a> <a class="a-link" style="color:var(--danger);" href="?delete=<?= (int)$k['id'] ?>&token=<?= urlencode($csrf) ?>" onclick="return confirm('<?= __('api_delete_confirm') ?>');"><?= __('btn_delete') ?></a>

View file

@ -71,7 +71,7 @@ foreach (['voucher_created', 'voucher_bulk', 'user_login', 'user_logout', 'user_
<!-- Filter --> <!-- Filter -->
<div class="card" style="margin-bottom: 20px;"> <div class="card" style="margin-bottom: 20px;">
<div class="card-header"><span class="card-title"><i class="fas fa-filter"></i> <?= __('audit_filter') ?></span></div> <div class="card-header"><span class="card-title"><i class="fas fa-filter" aria-hidden="true"></i> <?= __('audit_filter') ?></span></div>
<div style="padding: 20px 25px;"> <div style="padding: 20px 25px;">
<form method="get" class="filter-bar"> <form method="get" class="filter-bar">
<div> <div>
@ -96,8 +96,8 @@ foreach (['voucher_created', 'voucher_bulk', 'user_login', 'user_logout', 'user_
<?php endforeach; ?> <?php endforeach; ?>
</select> </select>
</div> </div>
<button type="submit" class="btn btn-primary btn-small"><i class="fas fa-search"></i> Filtern</button> <button type="submit" class="btn btn-primary btn-small"><i class="fas fa-search" aria-hidden="true"></i> Filtern</button>
<a href="audit_log.php" class="btn btn-secondary btn-small"><i class="fas fa-times"></i> Zurücksetzen</a> <a href="audit_log.php" class="btn btn-secondary btn-small"><i class="fas fa-times" aria-hidden="true"></i> Zurücksetzen</a>
</form> </form>
</div> </div>
</div> </div>
@ -105,15 +105,15 @@ foreach (['voucher_created', 'voucher_bulk', 'user_login', 'user_logout', 'user_
<!-- Log-Tabelle --> <!-- Log-Tabelle -->
<div class="card"> <div class="card">
<div class="card-header"> <div class="card-header">
<span class="card-title"><i class="fas fa-history"></i> <?= __('audit_title') ?></span> <span class="card-title"><i class="fas fa-history" aria-hidden="true"></i> <?= __('audit_title') ?></span>
<span style="font-size:13px;color:var(--text-muted);"><?= number_format($total) ?> Einträge</span> <span style="font-size:13px;color:var(--text-muted);"><?= number_format($total) ?> Einträge</span>
</div> </div>
<?php if (empty($logs)): ?> <?php if (empty($logs)): ?>
<div class="empty-state"><i class="fas fa-history"></i><p><?= __('audit_none') ?></p></div> <div class="empty-state"><i class="fas fa-history" aria-hidden="true"></i><p><?= __('audit_none') ?></p></div>
<?php else: ?> <?php else: ?>
<div style="overflow-x:auto;"> <div style="overflow-x:auto;">
<div class="table-container"> <div class="table-container">
<table class="table"> <table class="table table-stack">
<thead> <thead>
<tr> <tr>
<th><?= __('audit_time') ?></th> <th><?= __('audit_time') ?></th>
@ -127,34 +127,34 @@ foreach (['voucher_created', 'voucher_bulk', 'user_login', 'user_logout', 'user_
<tbody> <tbody>
<?php foreach ($logs as $log): ?> <?php foreach ($logs as $log): ?>
<tr> <tr>
<td style="white-space:nowrap;color:var(--text-muted);"> <td data-label="<?= __('audit_time') ?>" style="white-space:nowrap;color:var(--text-muted);">
<?= date('d.m.Y', strtotime($log['created_at'])) ?><br> <?= date('d.m.Y', strtotime($log['created_at'])) ?><br>
<small><?= date('H:i:s', strtotime($log['created_at'])) ?></small> <small><?= date('H:i:s', strtotime($log['created_at'])) ?></small>
</td> </td>
<td> <td data-label="<?= __('audit_action') ?>">
<span class="action-chip"> <span class="action-chip">
<?= htmlspecialchars($actionLabels[$log['action']] ?? $log['action']) ?> <?= htmlspecialchars($actionLabels[$log['action']] ?? $log['action']) ?>
</span> </span>
</td> </td>
<td> <td data-label="<?= __('audit_user') ?>">
<?php if ($log['user_name']): ?> <?php if ($log['user_name']): ?>
<strong style="font-size:13px;"><?= htmlspecialchars($log['user_name']) ?></strong><br> <strong style="font-size:13px;"><?= htmlspecialchars($log['user_name']) ?></strong><br>
<small style="color:var(--text-muted);"><?= htmlspecialchars($log['user_email'] ?? '') ?></small> <small style="color:var(--text-muted);"><?= htmlspecialchars($log['user_email'] ?? '') ?></small>
<?php else: ?> <?php else: ?>
<em style="color:var(--text-muted);">System/Anonym</em> <em style="color:var(--text-muted);"><?= __('audit_system_anon') ?></em>
<?php endif; ?> <?php endif; ?>
</td> </td>
<td style="color:var(--text-secondary);"> <td data-label="<?= __('audit_entity') ?>" style="color:var(--text-secondary);">
<?php if ($log['entity_type']): ?> <?php if ($log['entity_type']): ?>
<code style="font-size:11px;"><?= htmlspecialchars($log['entity_type']) ?>:<?= htmlspecialchars($log['entity_id'] ?? '') ?></code> <code style="font-size:11px;"><?= htmlspecialchars($log['entity_type']) ?>:<?= htmlspecialchars($log['entity_id'] ?? '') ?></code>
<?php else: ?> <?php else: ?>
<span style="color:var(--text-muted);">-</span> <span style="color:var(--text-muted);">-</span>
<?php endif; ?> <?php endif; ?>
</td> </td>
<td class="details-cell" title="<?= htmlspecialchars($log['details'] ?? '') ?>"> <td class="details-cell" data-label="<?= __('audit_details') ?>" title="<?= htmlspecialchars($log['details'] ?? '') ?>">
<?= htmlspecialchars(mb_strimwidth($log['details'] ?? '-', 0, 80, '…')) ?> <?= htmlspecialchars(mb_strimwidth($log['details'] ?? '-', 0, 80, '…')) ?>
</td> </td>
<td class="ip-cell"><?= htmlspecialchars($log['ip_address'] ?? '-') ?></td> <td class="ip-cell" data-label="<?= __('audit_ip') ?>"><?= htmlspecialchars($log['ip_address'] ?? '-') ?></td>
</tr> </tr>
<?php endforeach; ?> <?php endforeach; ?>
</tbody> </tbody>
@ -169,13 +169,13 @@ foreach (['voucher_created', 'voucher_bulk', 'user_login', 'user_logout', 'user_
<?php <?php
$baseUrl = '?' . http_build_query(array_filter(['action' => $filterAction, 'user_id' => $filterUser])); $baseUrl = '?' . http_build_query(array_filter(['action' => $filterAction, 'user_id' => $filterUser]));
if ($page > 1): ?> if ($page > 1): ?>
<a href="<?= $baseUrl ?>&page=<?= $page - 1 ?>" class="page-btn"><i class="fas fa-chevron-left"></i></a> <a href="<?= $baseUrl ?>&page=<?= $page - 1 ?>" class="page-btn"><i class="fas fa-chevron-left" aria-hidden="true"></i></a>
<?php endif; <?php endif;
for ($p = max(1, $page - 2); $p <= min($pages, $page + 2); $p++): ?> for ($p = max(1, $page - 2); $p <= min($pages, $page + 2); $p++): ?>
<a href="<?= $baseUrl ?>&page=<?= $p ?>" class="page-btn <?= $p === $page ? 'active' : '' ?>"><?= $p ?></a> <a href="<?= $baseUrl ?>&page=<?= $p ?>" class="page-btn <?= $p === $page ? 'active' : '' ?>"><?= $p ?></a>
<?php endfor; <?php endfor;
if ($page < $pages): ?> if ($page < $pages): ?>
<a href="<?= $baseUrl ?>&page=<?= $page + 1 ?>" class="page-btn"><i class="fas fa-chevron-right"></i></a> <a href="<?= $baseUrl ?>&page=<?= $page + 1 ?>" class="page-btn"><i class="fas fa-chevron-right" aria-hidden="true"></i></a>
<?php endif; ?> <?php endif; ?>
</div> </div>
</div> </div>

View file

@ -105,7 +105,7 @@ $currentPage = 'dashboard';
<span id="liveStatus">DB</span> <span id="liveStatus">DB</span>
</span> </span>
<button onclick="refreshData('live')" class="btn btn-secondary btn-sm" id="refreshBtn"> <button onclick="refreshData('live')" class="btn btn-secondary btn-sm" id="refreshBtn">
<i class="fas fa-sync-alt"></i> <?= __('dashboard_live_refresh') ?> <i class="fas fa-sync-alt" aria-hidden="true"></i> <?= __('dashboard_live_refresh') ?>
</button> </button>
</div> </div>
</div> </div>
@ -114,21 +114,21 @@ $currentPage = 'dashboard';
<div class="stat-card"> <div class="stat-card">
<div class="stat-card-header"> <div class="stat-card-header">
<div class="stat-card-title"><?= __('dashboard_active_sites') ?></div> <div class="stat-card-title"><?= __('dashboard_active_sites') ?></div>
<div class="stat-card-icon info"><i class="fas fa-location-dot"></i></div> <div class="stat-card-icon info"><i class="fas fa-location-dot" aria-hidden="true"></i></div>
</div> </div>
<div class="stat-card-value"><?= $stats['total_sites'] ?></div> <div class="stat-card-value"><?= $stats['total_sites'] ?></div>
</div> </div>
<div class="stat-card"> <div class="stat-card">
<div class="stat-card-header"> <div class="stat-card-header">
<div class="stat-card-title"><?= __('dashboard_users') ?></div> <div class="stat-card-title"><?= __('dashboard_users') ?></div>
<div class="stat-card-icon accent"><i class="fas fa-users"></i></div> <div class="stat-card-icon accent"><i class="fas fa-users" aria-hidden="true"></i></div>
</div> </div>
<div class="stat-card-value"><?= $stats['total_users'] ?></div> <div class="stat-card-value"><?= $stats['total_users'] ?></div>
</div> </div>
<div class="stat-card live"> <div class="stat-card live">
<div class="stat-card-header"> <div class="stat-card-header">
<div class="stat-card-title"><?= __('dashboard_valid') ?></div> <div class="stat-card-title"><?= __('dashboard_valid') ?></div>
<div class="stat-card-icon success"><i class="fas fa-circle-check"></i></div> <div class="stat-card-icon success"><i class="fas fa-circle-check" aria-hidden="true"></i></div>
</div> </div>
<div class="stat-card-value valid" id="liveValid"><?= (int)($voucherStats['valid']??0) ?></div> <div class="stat-card-value valid" id="liveValid"><?= (int)($voucherStats['valid']??0) ?></div>
<div class="stat-card-sub" id="subValid"><?= $lastCronSync ? date('H:i', strtotime($lastCronSync)) : __('never') ?></div> <div class="stat-card-sub" id="subValid"><?= $lastCronSync ? date('H:i', strtotime($lastCronSync)) : __('never') ?></div>
@ -136,21 +136,21 @@ $currentPage = 'dashboard';
<div class="stat-card live"> <div class="stat-card live">
<div class="stat-card-header"> <div class="stat-card-header">
<div class="stat-card-title"><?= __('dashboard_used') ?></div> <div class="stat-card-title"><?= __('dashboard_used') ?></div>
<div class="stat-card-icon warning"><i class="fas fa-user-check"></i></div> <div class="stat-card-icon warning"><i class="fas fa-user-check" aria-hidden="true"></i></div>
</div> </div>
<div class="stat-card-value used" id="liveUsed"><?= (int)($voucherStats['used']??0) ?></div> <div class="stat-card-value used" id="liveUsed"><?= (int)($voucherStats['used']??0) ?></div>
</div> </div>
<div class="stat-card live"> <div class="stat-card live">
<div class="stat-card-header"> <div class="stat-card-header">
<div class="stat-card-title"><?= __('dashboard_expired') ?></div> <div class="stat-card-title"><?= __('dashboard_expired') ?></div>
<div class="stat-card-icon danger"><i class="fas fa-circle-xmark"></i></div> <div class="stat-card-icon danger"><i class="fas fa-circle-xmark" aria-hidden="true"></i></div>
</div> </div>
<div class="stat-card-value expired" id="liveExpired"><?= (int)($voucherStats['expired']??0) ?></div> <div class="stat-card-value expired" id="liveExpired"><?= (int)($voucherStats['expired']??0) ?></div>
</div> </div>
<div class="stat-card live"> <div class="stat-card live">
<div class="stat-card-header"> <div class="stat-card-header">
<div class="stat-card-title"><?= __('dashboard_total') ?></div> <div class="stat-card-title"><?= __('dashboard_total') ?></div>
<div class="stat-card-icon"><i class="fas fa-ticket"></i></div> <div class="stat-card-icon"><i class="fas fa-ticket" aria-hidden="true"></i></div>
</div> </div>
<div class="stat-card-value" id="liveTotal"><?= (int)($voucherStats['total']??0) ?></div> <div class="stat-card-value" id="liveTotal"><?= (int)($voucherStats['total']??0) ?></div>
</div> </div>
@ -183,9 +183,9 @@ $currentPage = 'dashboard';
<?php endforeach; ?> <?php endforeach; ?>
<?php if (empty($sites)): ?> <?php if (empty($sites)): ?>
<div class="empty-state" style="grid-column:1/-1;"> <div class="empty-state" style="grid-column:1/-1;">
<div class="empty-icon"><i class="fas fa-location-dot"></i></div> <div class="empty-icon"><i class="fas fa-location-dot" aria-hidden="true"></i></div>
<p><?= __('dashboard_no_data') ?></p> <p><?= __('dashboard_no_data') ?></p>
<a href="sites.php" class="btn btn-primary" style="margin-top:15px;"><i class="fas fa-plus"></i> <?= __('sites_add') ?></a> <a href="sites.php" class="btn btn-primary" style="margin-top:15px;"><i class="fas fa-plus" aria-hidden="true"></i> <?= __('sites_add') ?></a>
</div> </div>
<?php endif; ?> <?php endif; ?>
</div> </div>
@ -204,7 +204,7 @@ $currentPage = 'dashboard';
<div class="card-header"><h2 class="card-title"><?= __('dashboard_top_users') ?></h2></div> <div class="card-header"><h2 class="card-title"><?= __('dashboard_top_users') ?></h2></div>
<div class="card-body"> <div class="card-body">
<?php if (empty($topUsers)): ?> <?php if (empty($topUsers)): ?>
<div class="empty-state"><i class="fas fa-users"></i><p><?= __('dashboard_no_data') ?></p></div> <div class="empty-state"><i class="fas fa-users" aria-hidden="true"></i><p><?= __('dashboard_no_data') ?></p></div>
<?php else: ?> <?php else: ?>
<ul class="top-users-list"> <ul class="top-users-list">
<?php foreach ($topUsers as $u): ?> <?php foreach ($topUsers as $u): ?>
@ -225,21 +225,21 @@ $currentPage = 'dashboard';
</div> </div>
<div class="card"> <div class="card">
<div class="card-header"><h2 class="card-title"><?= __('dashboard_recent') ?></h2><a href="vouchers.php" class="btn btn-secondary btn-sm"><i class="fas fa-arrow-up-right-from-square"></i> Live</a></div> <div class="card-header"><h2 class="card-title"><?= __('dashboard_recent') ?></h2><a href="vouchers.php" class="btn btn-secondary btn-sm"><i class="fas fa-arrow-up-right-from-square" aria-hidden="true"></i> Live</a></div>
<div class="card-body" style="padding:0;"> <div class="card-body" style="padding:0;">
<?php if (empty($recentVouchers)): ?> <?php if (empty($recentVouchers)): ?>
<div class="empty-state"><i class="fas fa-ticket-alt"></i><p><?= __('dashboard_no_vouchers') ?></p></div> <div class="empty-state"><i class="fas fa-ticket-alt" aria-hidden="true"></i><p><?= __('dashboard_no_vouchers') ?></p></div>
<?php else: ?> <?php else: ?>
<div class="table-container"> <div class="table-container">
<table class="table"> <table class="table table-stack">
<thead><tr><th><?= __('label_created') ?></th><th><?= __('label_code') ?></th><th><?= __('label_site') ?></th><th><?= __('label_status') ?></th></tr></thead> <thead><tr><th><?= __('label_created') ?></th><th><?= __('label_code') ?></th><th><?= __('label_site') ?></th><th><?= __('label_status') ?></th></tr></thead>
<tbody> <tbody>
<?php foreach ($recentVouchers as $v): ?> <?php foreach ($recentVouchers as $v): ?>
<tr> <tr>
<td style="font-size:12px;"><?= date('d.m H:i', strtotime($v['created_at'])) ?></td> <td data-label="<?= __('label_created') ?>" style="font-size:12px;"><?= date('d.m H:i', strtotime($v['created_at'])) ?></td>
<td><code><?= htmlspecialchars($v['voucher_code']) ?></code></td> <td data-label="<?= __('label_code') ?>"><code><?= htmlspecialchars($v['voucher_code']) ?></code></td>
<td><span class="badge badge-info"><?= htmlspecialchars($v['site_name']??'') ?></span></td> <td data-label="<?= __('label_site') ?>"><span class="badge badge-info"><?= htmlspecialchars($v['site_name']??'') ?></span></td>
<td> <td data-label="<?= __('label_status') ?>">
<?php $st=$v['status']??'valid'; ?> <?php $st=$v['status']??'valid'; ?>
<span class="badge badge-<?= $st==='valid'?'success':($st==='used'?'warning':'danger') ?>"><?= __('status_'.$st) ?></span> <span class="badge badge-<?= $st==='valid'?'success':($st==='used'?'warning':'danger') ?>"><?= __('status_'.$st) ?></span>
</td> </td>
@ -255,7 +255,7 @@ $currentPage = 'dashboard';
</main> </main>
<div id="toast-container"></div> <div id="toast-container" role="status" aria-live="polite"></div>
<script src="../assets/global.js"></script> <script src="../assets/global.js"></script>
<script> <script>
const rootStyles = getComputedStyle(document.documentElement); const rootStyles = getComputedStyle(document.documentElement);
@ -302,7 +302,7 @@ async function refreshData(mode='db') {
liveBadge.className = 'live-badge loading'; liveBadge.className = 'live-badge loading';
liveStatus.textContent = '...'; liveStatus.textContent = '...';
refreshBtn.disabled = true; refreshBtn.disabled = true;
refreshBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i>'; refreshBtn.innerHTML = '<i class="fas fa-spinner fa-spin" aria-hidden="true"></i>';
try { try {
const url = `index.php?ajax_stats=1${mode==='live'?'&sync=1':''}`; const url = `index.php?ajax_stats=1${mode==='live'?'&sync=1':''}`;
const result = await fetch(url).then(r=>r.json()); const result = await fetch(url).then(r=>r.json());
@ -340,7 +340,7 @@ async function refreshData(mode='db') {
} }
isLoading = false; isLoading = false;
refreshBtn.disabled = false; refreshBtn.disabled = false;
refreshBtn.innerHTML = '<i class="fas fa-sync-alt"></i> <?= __('dashboard_live_refresh') ?>'; refreshBtn.innerHTML = '<i class="fas fa-sync-alt" aria-hidden="true"></i> <?= __('dashboard_live_refresh') ?>';
} }
</script> </script>
</body> </body>

View file

@ -115,10 +115,10 @@ $adminBase = '';
<?php endforeach; ?> <?php endforeach; ?>
</select> </select>
</form> </form>
<a class="btn btn-secondary" href="?export=daily&days=<?= $days ?>"><i class="fas fa-download"></i> <?= __('rep_csv_daily') ?></a> <a class="btn btn-secondary" href="?export=daily&days=<?= $days ?>"><i class="fas fa-download" aria-hidden="true"></i> <?= __('rep_csv_daily') ?></a>
<a class="btn btn-secondary" href="?export=per_site"><i class="fas fa-download"></i> <?= __('rep_csv_site') ?></a> <a class="btn btn-secondary" href="?export=per_site"><i class="fas fa-download" aria-hidden="true"></i> <?= __('rep_csv_site') ?></a>
<a class="btn btn-secondary" href="?export=per_user"><i class="fas fa-download"></i> <?= __('rep_csv_user') ?></a> <a class="btn btn-secondary" href="?export=per_user"><i class="fas fa-download" aria-hidden="true"></i> <?= __('rep_csv_user') ?></a>
<button class="btn btn-secondary" onclick="window.print()"><i class="fas fa-print"></i> <?= __('rep_print') ?></button> <button class="btn btn-secondary" onclick="window.print()"><i class="fas fa-print" aria-hidden="true"></i> <?= __('rep_print') ?></button>
</div> </div>
<div class="grid4"> <div class="grid4">

View file

@ -104,7 +104,7 @@ $activeSessions = $dbSessions ? $auth->activeSessionCount() : 0;
<body class="app-body focus-page"> <body class="app-body focus-page">
<div class="focus-card card"> <div class="focus-card card">
<div class="focus-head"> <div class="focus-head">
<span class="focus-icon"><i class="fas fa-shield-halved"></i></span> <span class="focus-icon"><i class="fas fa-shield-halved" aria-hidden="true"></i></span>
<div> <div>
<h1><?= __('sec_title') ?></h1> <h1><?= __('sec_title') ?></h1>
<p class="sub"><?= __('sec_account') ?> <?= htmlspecialchars($user['email']) ?></p> <p class="sub"><?= __('sec_account') ?> <?= htmlspecialchars($user['email']) ?></p>
@ -119,7 +119,7 @@ $activeSessions = $dbSessions ? $auth->activeSessionCount() : 0;
<?php if (!empty($backupCodes)): ?> <?php if (!empty($backupCodes)): ?>
<div class="codes-box"> <div class="codes-box">
<strong><i class="fas fa-key"></i> <?= __('sec_recovery_codes') ?></strong> <strong><i class="fas fa-key" aria-hidden="true"></i> <?= __('sec_recovery_codes') ?></strong>
<p><?= __('sec_recovery_hint') ?></p> <p><?= __('sec_recovery_hint') ?></p>
<div class="codes"> <div class="codes">
<?php foreach ($backupCodes as $c): ?><span><?= htmlspecialchars($c) ?></span><?php endforeach; ?> <?php foreach ($backupCodes as $c): ?><span><?= htmlspecialchars($c) ?></span><?php endforeach; ?>
@ -128,10 +128,10 @@ $activeSessions = $dbSessions ? $auth->activeSessionCount() : 0;
<?php endif; ?> <?php endif; ?>
<?php if (!$hasPassword): ?> <?php if (!$hasPassword): ?>
<div class="status off"><i class="fas fa-circle-minus"></i> <?= __('sec_unavailable') ?></div> <div class="status off"><i class="fas fa-circle-minus" aria-hidden="true"></i> <?= __('sec_unavailable') ?></div>
<p class="sub"><?= __('sec_m365_hint') ?></p> <p class="sub"><?= __('sec_m365_hint') ?></p>
<?php elseif ($totpEnabled): ?> <?php elseif ($totpEnabled): ?>
<div class="status on"><i class="fas fa-circle-check"></i> <?= __('sec_active') ?></div> <div class="status on"><i class="fas fa-circle-check" aria-hidden="true"></i> <?= __('sec_active') ?></div>
<p class="sub"><?= __('sec_active_hint') ?><br> <p class="sub"><?= __('sec_active_hint') ?><br>
<?= __('sec_codes_left') ?> <strong><?= (int)$auth->backupCodesRemaining($user) ?></strong></p> <?= __('sec_codes_left') ?> <strong><?= (int)$auth->backupCodesRemaining($user) ?></strong></p>
<form method="post" style="margin-bottom:10px;"> <form method="post" style="margin-bottom:10px;">
@ -143,7 +143,7 @@ $activeSessions = $dbSessions ? $auth->activeSessionCount() : 0;
<button type="submit" name="disable_totp" class="btn btn-danger btn-lg btn-block"><?= __('sec_disable') ?></button> <button type="submit" name="disable_totp" class="btn btn-danger btn-lg btn-block"><?= __('sec_disable') ?></button>
</form> </form>
<?php else: ?> <?php else: ?>
<div class="status off"><i class="fas fa-circle-minus"></i> <?= __('sec_inactive') ?></div> <div class="status off"><i class="fas fa-circle-minus" aria-hidden="true"></i> <?= __('sec_inactive') ?></div>
<ol> <ol>
<li><?= __('sec_step_1') ?></li> <li><?= __('sec_step_1') ?></li>
<li><?= __('sec_step_2') ?></li> <li><?= __('sec_step_2') ?></li>
@ -175,7 +175,7 @@ $activeSessions = $dbSessions ? $auth->activeSessionCount() : 0;
</form> </form>
<?php endif; ?> <?php endif; ?>
<div class="auth-links"><a class="back-link" href="../index.php"><i class="fas fa-arrow-left"></i> <?= __('nav_back') ?></a></div> <div class="auth-links"><a class="back-link" href="../index.php"><i class="fas fa-arrow-left" aria-hidden="true"></i> <?= __('nav_back') ?></a></div>
</div> </div>
</body> </body>
</html> </html>

View file

@ -277,7 +277,7 @@ function imageField(string $name, string $label, string $value, string $hint = '
<?php if ($preview !== ''): ?> <?php if ($preview !== ''): ?>
<img src="<?= htmlspecialchars($preview) ?>" alt=""> <img src="<?= htmlspecialchars($preview) ?>" alt="">
<?php else: ?> <?php else: ?>
<i class="fas fa-image"></i> <i class="fas fa-image" aria-hidden="true"></i>
<?php endif; ?> <?php endif; ?>
</div> </div>
<div class="image-field-controls"> <div class="image-field-controls">
@ -318,29 +318,29 @@ $adminBase = '';
</div> </div>
<?php if ($error): ?> <?php if ($error): ?>
<div class="alert alert-error"><i class="fas fa-exclamation-circle"></i><span><?= htmlspecialchars($error) ?></span></div> <div class="alert alert-error"><i class="fas fa-exclamation-circle" aria-hidden="true"></i><span><?= htmlspecialchars($error) ?></span></div>
<?php endif; ?> <?php endif; ?>
<?php if ($success): ?> <?php if ($success): ?>
<div class="alert alert-success"><i class="fas fa-check-circle"></i><span><?= htmlspecialchars($success) ?></span></div> <div class="alert alert-success"><i class="fas fa-check-circle" aria-hidden="true"></i><span><?= htmlspecialchars($success) ?></span></div>
<?php endif; ?> <?php endif; ?>
<div class="tab-container"> <div class="tab-container">
<div class="tab-navigation" id="tabNav"> <div class="tab-navigation" id="tabNav">
<button class="tab-button active" data-tab="general"><i class="fas fa-sliders-h"></i> <?= __('settings_tab_general') ?></button> <button class="tab-button active" data-tab="general"><i class="fas fa-sliders-h" aria-hidden="true"></i> <?= __('settings_tab_general') ?></button>
<button class="tab-button" data-tab="defaults"><i class="fas fa-sliders-h"></i> <?= __('settings_tab_defaults') ?></button> <button class="tab-button" data-tab="defaults"><i class="fas fa-sliders-h" aria-hidden="true"></i> <?= __('settings_tab_defaults') ?></button>
<button class="tab-button" data-tab="branding"><i class="fas fa-palette"></i> <?= __('settings_tab_branding') ?></button> <button class="tab-button" data-tab="branding"><i class="fas fa-palette" aria-hidden="true"></i> <?= __('settings_tab_branding') ?></button>
<button class="tab-button" data-tab="login"><i class="fas fa-right-to-bracket"></i> <?= __('settings_tab_login') ?></button> <button class="tab-button" data-tab="login"><i class="fas fa-right-to-bracket" aria-hidden="true"></i> <?= __('settings_tab_login') ?></button>
<button class="tab-button" data-tab="cron"><i class="fas fa-clock"></i> <?= __('settings_tab_cron') ?></button> <button class="tab-button" data-tab="cron"><i class="fas fa-clock" aria-hidden="true"></i> <?= __('settings_tab_cron') ?></button>
<button class="tab-button" data-tab="m365"><i class="fab fa-microsoft"></i> <?= __('settings_tab_m365') ?></button> <button class="tab-button" data-tab="m365"><i class="fab fa-microsoft" aria-hidden="true"></i> <?= __('settings_tab_m365') ?></button>
<button class="tab-button" data-tab="smtp"><i class="fas fa-envelope"></i> <?= __('settings_tab_smtp') ?></button> <button class="tab-button" data-tab="smtp"><i class="fas fa-envelope" aria-hidden="true"></i> <?= __('settings_tab_smtp') ?></button>
<button class="tab-button" data-tab="templates_email"><i class="fas fa-file-alt"></i> <?= __('settings_tab_templates_email') ?></button> <button class="tab-button" data-tab="templates_email"><i class="fas fa-file-alt" aria-hidden="true"></i> <?= __('settings_tab_templates_email') ?></button>
<button class="tab-button" data-tab="system"><i class="fas fa-cogs"></i> <?= __('settings_tab_system') ?></button> <button class="tab-button" data-tab="system"><i class="fas fa-cogs" aria-hidden="true"></i> <?= __('settings_tab_system') ?></button>
<button class="tab-button" data-tab="password"><i class="fas fa-key"></i> <?= __('settings_tab_password') ?></button> <button class="tab-button" data-tab="password"><i class="fas fa-key" aria-hidden="true"></i> <?= __('settings_tab_password') ?></button>
</div> </div>
<!-- Allgemein --> <!-- Allgemein -->
<div id="tab-general" class="tab-content active"> <div id="tab-general" class="tab-content active">
<h2 style="margin-bottom: 20px; color: var(--text-primary);"><i class="fas fa-sliders-h"></i> <?= __('settings_tab_general') ?></h2> <h2 style="margin-bottom: 20px; color: var(--text-primary);"><i class="fas fa-sliders-h" aria-hidden="true"></i> <?= __('settings_tab_general') ?></h2>
<form method="post" enctype="multipart/form-data"> <form method="post" enctype="multipart/form-data">
<input type="hidden" name="csrf_token" value="<?= $auth->getCsrfToken() ?>"> <input type="hidden" name="csrf_token" value="<?= $auth->getCsrfToken() ?>">
<input type="hidden" name="form_type" value="general"> <input type="hidden" name="form_type" value="general">
@ -353,13 +353,13 @@ $adminBase = '';
<div class="form-group"><label><?= __('settings_instr_header') ?></label><input type="text" name="instruction_header" value="<?= htmlspecialchars($cs['instruction_header']) ?>"></div> <div class="form-group"><label><?= __('settings_instr_header') ?></label><input type="text" name="instruction_header" value="<?= htmlspecialchars($cs['instruction_header']) ?>"></div>
<div class="form-group"><label><?= __('settings_instr_text') ?></label><textarea name="instruction_text" class="tinymce-editor"><?= htmlspecialchars($cs['instruction_text']) ?></textarea></div> <div class="form-group"><label><?= __('settings_instr_text') ?></label><textarea name="instruction_text" class="tinymce-editor"><?= htmlspecialchars($cs['instruction_text']) ?></textarea></div>
<div class="checkbox-group" style="margin-bottom: 20px;"><input type="checkbox" name="public_access" id="public_access" <?= $cs['public_access'] == '1' ? 'checked' : '' ?>><label for="public_access" style="margin:0;"><?= __('settings_public_access') ?></label></div> <div class="checkbox-group" style="margin-bottom: 20px;"><input type="checkbox" name="public_access" id="public_access" <?= $cs['public_access'] == '1' ? 'checked' : '' ?>><label for="public_access" style="margin:0;"><?= __('settings_public_access') ?></label></div>
<button type="submit" name="save_settings" class="btn btn-primary"><i class="fas fa-save"></i> <?= __('btn_save') ?></button> <button type="submit" name="save_settings" class="btn btn-primary"><i class="fas fa-save" aria-hidden="true"></i> <?= __('btn_save') ?></button>
</form> </form>
</div> </div>
<!-- Voucher-Standards --> <!-- Voucher-Standards -->
<div id="tab-defaults" class="tab-content"> <div id="tab-defaults" class="tab-content">
<h2 style="margin-bottom: 8px; color: var(--text-primary);"><i class="fas fa-sliders-h"></i> <?= __('settings_tab_defaults') ?></h2> <h2 style="margin-bottom: 8px; color: var(--text-primary);"><i class="fas fa-sliders-h" aria-hidden="true"></i> <?= __('settings_tab_defaults') ?></h2>
<p style="color: var(--text-muted); font-size: 14px; margin-bottom: 24px;"><?= __('settings_defaults_hint') ?></p> <p style="color: var(--text-muted); font-size: 14px; margin-bottom: 24px;"><?= __('settings_defaults_hint') ?></p>
<form method="post"> <form method="post">
<input type="hidden" name="csrf_token" value="<?= $auth->getCsrfToken() ?>"> <input type="hidden" name="csrf_token" value="<?= $auth->getCsrfToken() ?>">
@ -382,20 +382,20 @@ $adminBase = '';
</div> </div>
</div> </div>
<div class="info-box" style="margin-top: 10px;"> <div class="info-box" style="margin-top: 10px;">
<h4><i class="fas fa-info-circle"></i> Gültigkeits-Referenz</h4> <h4><i class="fas fa-info-circle" aria-hidden="true"></i> Gültigkeits-Referenz</h4>
<p> <p>
60 Min = 1 Stunde &nbsp;|&nbsp; 480 Min = 8 Stunden &nbsp;|&nbsp; 60 Min = 1 Stunde &nbsp;|&nbsp; 480 Min = 8 Stunden &nbsp;|&nbsp;
1440 Min = 1 Tag &nbsp;|&nbsp; 10080 Min = 1 Woche &nbsp;|&nbsp; 1440 Min = 1 Tag &nbsp;|&nbsp; 10080 Min = 1 Woche &nbsp;|&nbsp;
43200 Min = 30 Tage 43200 Min = 30 Tage
</p> </p>
</div> </div>
<button type="submit" name="save_settings" class="btn btn-primary" style="margin-top: 16px;"><i class="fas fa-save"></i> <?= __('btn_save') ?></button> <button type="submit" name="save_settings" class="btn btn-primary" style="margin-top: 16px;"><i class="fas fa-save" aria-hidden="true"></i> <?= __('btn_save') ?></button>
</form> </form>
</div> </div>
<!-- Design & Branding --> <!-- Design & Branding -->
<div id="tab-branding" class="tab-content"> <div id="tab-branding" class="tab-content">
<h2 style="margin-bottom: 8px; color: var(--text-primary);"><i class="fas fa-palette"></i> <?= __('settings_tab_branding') ?></h2> <h2 style="margin-bottom: 8px; color: var(--text-primary);"><i class="fas fa-palette" aria-hidden="true"></i> <?= __('settings_tab_branding') ?></h2>
<p style="color: var(--text-muted); font-size: 14px; margin-bottom: 24px;"><?= __('settings_branding_intro') ?></p> <p style="color: var(--text-muted); font-size: 14px; margin-bottom: 24px;"><?= __('settings_branding_intro') ?></p>
<form method="post"> <form method="post">
<input type="hidden" name="csrf_token" value="<?= $auth->getCsrfToken() ?>"> <input type="hidden" name="csrf_token" value="<?= $auth->getCsrfToken() ?>">
@ -449,8 +449,8 @@ $adminBase = '';
<div class="card" id="brandPreview" style="margin-top: 8px;"> <div class="card" id="brandPreview" style="margin-top: 8px;">
<h3><?= __('settings_brand_preview') ?></h3> <h3><?= __('settings_brand_preview') ?></h3>
<div style="display:flex;flex-wrap:wrap;gap:12px;align-items:center;margin-top:14px;"> <div style="display:flex;flex-wrap:wrap;gap:12px;align-items:center;margin-top:14px;">
<span class="brand-mark" style="width:38px;height:38px;"><i class="fas fa-wifi"></i></span> <span class="brand-mark" style="width:38px;height:38px;"><i class="fas fa-wifi" aria-hidden="true"></i></span>
<button type="button" class="btn btn-primary"><i class="fas fa-ticket"></i> <?= __('voucher_create_btn') ?></button> <button type="button" class="btn btn-primary"><i class="fas fa-ticket" aria-hidden="true"></i> <?= __('voucher_create_btn') ?></button>
<button type="button" class="btn btn-secondary"><?= __('btn_cancel') ?></button> <button type="button" class="btn btn-secondary"><?= __('btn_cancel') ?></button>
<span class="badge badge-success"><?= __('status_valid') ?></span> <span class="badge badge-success"><?= __('status_valid') ?></span>
<span class="chip"><?= __('nav_vouchers') ?></span> <span class="chip"><?= __('nav_vouchers') ?></span>
@ -458,13 +458,13 @@ $adminBase = '';
</div> </div>
</div> </div>
<button type="submit" name="save_settings" class="btn btn-primary" style="margin-top:16px;"><i class="fas fa-save"></i> <?= __('btn_save') ?></button> <button type="submit" name="save_settings" class="btn btn-primary" style="margin-top:16px;"><i class="fas fa-save" aria-hidden="true"></i> <?= __('btn_save') ?></button>
</form> </form>
</div> </div>
<!-- Login-Seite --> <!-- Login-Seite -->
<div id="tab-login" class="tab-content"> <div id="tab-login" class="tab-content">
<h2 style="margin-bottom: 8px; color: var(--text-primary);"><i class="fas fa-right-to-bracket"></i> <?= __('settings_tab_login') ?></h2> <h2 style="margin-bottom: 8px; color: var(--text-primary);"><i class="fas fa-right-to-bracket" aria-hidden="true"></i> <?= __('settings_tab_login') ?></h2>
<p style="color: var(--text-muted); font-size: 14px; margin-bottom: 24px;"><?= __('settings_login_intro') ?></p> <p style="color: var(--text-muted); font-size: 14px; margin-bottom: 24px;"><?= __('settings_login_intro') ?></p>
<form method="post" enctype="multipart/form-data"> <form method="post" enctype="multipart/form-data">
<input type="hidden" name="csrf_token" value="<?= $auth->getCsrfToken() ?>"> <input type="hidden" name="csrf_token" value="<?= $auth->getCsrfToken() ?>">
@ -532,9 +532,9 @@ $adminBase = '';
</div> </div>
<div style="display:flex;gap:10px;flex-wrap:wrap;margin-top:16px;"> <div style="display:flex;gap:10px;flex-wrap:wrap;margin-top:16px;">
<button type="submit" name="save_settings" class="btn btn-primary"><i class="fas fa-save"></i> <?= __('btn_save') ?></button> <button type="submit" name="save_settings" class="btn btn-primary"><i class="fas fa-save" aria-hidden="true"></i> <?= __('btn_save') ?></button>
<a href="../login.php?preview=1" target="_blank" rel="noopener" class="btn btn-secondary"> <a href="../login.php?preview=1" target="_blank" rel="noopener" class="btn btn-secondary">
<i class="fas fa-arrow-up-right-from-square"></i> <?= __('settings_login_preview') ?> <i class="fas fa-arrow-up-right-from-square" aria-hidden="true"></i> <?= __('settings_login_preview') ?>
</a> </a>
</div> </div>
</form> </form>
@ -542,24 +542,24 @@ $adminBase = '';
<!-- Cron-Sync --> <!-- Cron-Sync -->
<div id="tab-cron" class="tab-content"> <div id="tab-cron" class="tab-content">
<h2 style="margin-bottom: 20px; color: var(--text-primary);"><i class="fas fa-clock"></i> <?= __('settings_tab_cron') ?></h2> <h2 style="margin-bottom: 20px; color: var(--text-primary);"><i class="fas fa-clock" aria-hidden="true"></i> <?= __('settings_tab_cron') ?></h2>
<div class="info-box"> <div class="info-box">
<h4><i class="fas fa-info-circle"></i> <?= __('settings_cron_what') ?></h4> <h4><i class="fas fa-info-circle" aria-hidden="true"></i> <?= __('settings_cron_what') ?></h4>
<p>Der Cron-Job synchronisiert automatisch alle Voucher von Ihren UniFi Controllern in die lokale Datenbank.</p> <p>Der Cron-Job synchronisiert automatisch alle Voucher von Ihren UniFi Controllern in die lokale Datenbank.</p>
</div> </div>
<hr class="section-divider"> <hr class="section-divider">
<?php if (empty($cs['cron_token'])): ?> <?php if (empty($cs['cron_token'])): ?>
<p style="color: var(--text-muted); margin-bottom: 15px;"><i class="fas fa-exclamation-triangle" style="color: var(--warning);"></i> Kein Token konfiguriert.</p> <p style="color: var(--text-muted); margin-bottom: 15px;"><i class="fas fa-exclamation-triangle" style="color: var(--warning);" aria-hidden="true"></i> Kein Token konfiguriert.</p>
<form method="post"> <form method="post">
<input type="hidden" name="csrf_token" value="<?= $auth->getCsrfToken() ?>"> <input type="hidden" name="csrf_token" value="<?= $auth->getCsrfToken() ?>">
<button type="submit" name="generate_cron_token" class="btn btn-primary"><i class="fas fa-key"></i> Token generieren</button> <button type="submit" name="generate_cron_token" class="btn btn-primary"><i class="fas fa-key" aria-hidden="true"></i> Token generieren</button>
</form> </form>
<?php else: ?> <?php else: ?>
<div class="token-box" style="margin-bottom: 15px;"><?= htmlspecialchars($cs['cron_token']) ?></div> <div class="token-box" style="margin-bottom: 15px;"><?= htmlspecialchars($cs['cron_token']) ?></div>
<div style="display: flex; gap: 10px; flex-wrap: wrap; margin-bottom: 20px;"> <div style="display: flex; gap: 10px; flex-wrap: wrap; margin-bottom: 20px;">
<button onclick="copyToClipboard('<?= htmlspecialchars($cs['cron_token']) ?>')" class="btn btn-secondary"><i class="fas fa-copy"></i> Kopieren</button> <button onclick="copyToClipboard('<?= htmlspecialchars($cs['cron_token']) ?>')" class="btn btn-secondary"><i class="fas fa-copy" aria-hidden="true"></i> Kopieren</button>
<form method="post" style="display:inline;"><input type="hidden" name="csrf_token" value="<?= $auth->getCsrfToken() ?>"><button type="submit" name="generate_cron_token" class="btn btn-secondary"><i class="fas fa-sync"></i> Neu generieren</button></form> <form method="post" style="display:inline;"><input type="hidden" name="csrf_token" value="<?= $auth->getCsrfToken() ?>"><button type="submit" name="generate_cron_token" class="btn btn-secondary"><i class="fas fa-sync" aria-hidden="true"></i> Neu generieren</button></form>
<form method="post" style="display:inline;" onsubmit="return confirm('<?= __('js_confirm_delete_token') ?>');"><input type="hidden" name="csrf_token" value="<?= $auth->getCsrfToken() ?>"><button type="submit" name="delete_cron_token" class="btn btn-secondary" style="color: var(--danger);"><i class="fas fa-trash"></i> Löschen</button></form> <form method="post" style="display:inline;" onsubmit="return confirm('<?= __('js_confirm_delete_token') ?>');"><input type="hidden" name="csrf_token" value="<?= $auth->getCsrfToken() ?>"><button type="submit" name="delete_cron_token" class="btn btn-secondary" style="color: var(--danger);"><i class="fas fa-trash" aria-hidden="true"></i> Löschen</button></form>
</div> </div>
<?php endif; ?> <?php endif; ?>
<?php <?php
@ -569,7 +569,7 @@ $adminBase = '';
<label>Cron-URL</label> <label>Cron-URL</label>
<div style="display:flex;gap:10px;"> <div style="display:flex;gap:10px;">
<input type="text" id="cronUrl" value="<?= htmlspecialchars($cronUrl) ?>" readonly> <input type="text" id="cronUrl" value="<?= htmlspecialchars($cronUrl) ?>" readonly>
<button onclick="copyToClipboard(document.getElementById('cronUrl').value)" class="btn btn-secondary"><i class="fas fa-copy"></i></button> <button onclick="copyToClipboard(document.getElementById('cronUrl').value)" class="btn btn-secondary"><i class="fas fa-copy" aria-hidden="true"></i></button>
</div> </div>
</div> </div>
<div class="placeholder-info" style="background: var(--bg-hover); border-color: var(--border-color);"> <div class="placeholder-info" style="background: var(--bg-hover); border-color: var(--border-color);">
@ -580,7 +580,7 @@ $adminBase = '';
</div> </div>
<?php if ($cs['cron_token']): ?> <?php if ($cs['cron_token']): ?>
<div style="margin-top: 20px;"> <div style="margin-top: 20px;">
<button onclick="testCronJob()" class="btn btn-primary" id="testCronBtn"><i class="fas fa-play"></i> Jetzt ausführen</button> <button onclick="testCronJob()" class="btn btn-primary" id="testCronBtn"><i class="fas fa-play" aria-hidden="true"></i> Jetzt ausführen</button>
<span id="testCronResult" style="margin-left: 15px;"></span> <span id="testCronResult" style="margin-left: 15px;"></span>
</div> </div>
<?php endif; ?> <?php endif; ?>
@ -589,9 +589,9 @@ $adminBase = '';
<!-- M365 --> <!-- M365 -->
<div id="tab-m365" class="tab-content"> <div id="tab-m365" class="tab-content">
<h2 style="margin-bottom: 20px; color: var(--text-primary);"><i class="fab fa-microsoft"></i> Microsoft 365</h2> <h2 style="margin-bottom: 20px; color: var(--text-primary);"><i class="fab fa-microsoft" aria-hidden="true"></i> Microsoft 365</h2>
<div class="info-box"> <div class="info-box">
<h4><i class="fas fa-info-circle"></i> Azure AD App</h4> <h4><i class="fas fa-info-circle" aria-hidden="true"></i> Azure AD App</h4>
<p>Redirect URI: <strong><?= $protocol . '://' . $host . $scriptPath ?>/m365_callback.php</strong></p> <p>Redirect URI: <strong><?= $protocol . '://' . $host . $scriptPath ?>/m365_callback.php</strong></p>
</div> </div>
<form method="post"> <form method="post">
@ -600,13 +600,13 @@ $adminBase = '';
<div class="form-group"><label>Client ID</label><input type="text" name="m365_client_id" value="<?= htmlspecialchars($cs['m365_client_id']) ?>"></div> <div class="form-group"><label>Client ID</label><input type="text" name="m365_client_id" value="<?= htmlspecialchars($cs['m365_client_id']) ?>"></div>
<div class="form-group"><label>Client Secret</label><input type="password" name="m365_client_secret" value="<?= htmlspecialchars($cs['m365_client_secret']) ?>"></div> <div class="form-group"><label>Client Secret</label><input type="password" name="m365_client_secret" value="<?= htmlspecialchars($cs['m365_client_secret']) ?>"></div>
<div class="form-group"><label>Tenant ID</label><input type="text" name="m365_tenant_id" value="<?= htmlspecialchars($cs['m365_tenant_id']) ?>"></div> <div class="form-group"><label>Tenant ID</label><input type="text" name="m365_tenant_id" value="<?= htmlspecialchars($cs['m365_tenant_id']) ?>"></div>
<button type="submit" name="save_settings" class="btn btn-primary"><i class="fas fa-save"></i> <?= __('btn_save') ?></button> <button type="submit" name="save_settings" class="btn btn-primary"><i class="fas fa-save" aria-hidden="true"></i> <?= __('btn_save') ?></button>
</form> </form>
</div> </div>
<!-- SMTP --> <!-- SMTP -->
<div id="tab-smtp" class="tab-content"> <div id="tab-smtp" class="tab-content">
<h2 style="margin-bottom: 20px; color: var(--text-primary);"><i class="fas fa-envelope"></i> SMTP</h2> <h2 style="margin-bottom: 20px; color: var(--text-primary);"><i class="fas fa-envelope" aria-hidden="true"></i> SMTP</h2>
<form method="post"> <form method="post">
<input type="hidden" name="csrf_token" value="<?= $auth->getCsrfToken() ?>"> <input type="hidden" name="csrf_token" value="<?= $auth->getCsrfToken() ?>">
<input type="hidden" name="form_type" value="smtp"> <input type="hidden" name="form_type" value="smtp">
@ -624,20 +624,20 @@ $adminBase = '';
<div class="form-group"><label>Absender E-Mail</label><input type="email" name="smtp_from_email" value="<?= htmlspecialchars($cs['smtp_from_email']) ?>"></div> <div class="form-group"><label>Absender E-Mail</label><input type="email" name="smtp_from_email" value="<?= htmlspecialchars($cs['smtp_from_email']) ?>"></div>
<div class="form-group"><label><?= __('settings_smtp_from_name') ?></label><input type="text" name="smtp_from_name" value="<?= htmlspecialchars($cs['smtp_from_name']) ?>"></div> <div class="form-group"><label><?= __('settings_smtp_from_name') ?></label><input type="text" name="smtp_from_name" value="<?= htmlspecialchars($cs['smtp_from_name']) ?>"></div>
</div> </div>
<button type="submit" name="save_settings" class="btn btn-primary"><i class="fas fa-save"></i> <?= __('btn_save') ?></button> <button type="submit" name="save_settings" class="btn btn-primary"><i class="fas fa-save" aria-hidden="true"></i> <?= __('btn_save') ?></button>
</form> </form>
<hr class="section-divider"> <hr class="section-divider">
<h3 style="margin-bottom:15px; color: var(--text-primary);">SMTP testen</h3> <h3 style="margin-bottom:15px; color: var(--text-primary);">SMTP testen</h3>
<div style="display:flex;gap:10px;align-items:flex-end;"> <div style="display:flex;gap:10px;align-items:flex-end;">
<div style="flex:1;"><label>Test-E-Mail senden an</label><input type="email" id="smtpTestEmail" placeholder="empfaenger@example.com" style="margin-top:6px;"></div> <div style="flex:1;"><label>Test-E-Mail senden an</label><input type="email" id="smtpTestEmail" placeholder="empfaenger@example.com" style="margin-top:6px;"></div>
<button onclick="testSmtp()" class="btn btn-secondary" id="smtpTestBtn"><i class="fas fa-paper-plane"></i> <?= __('btn_test') ?></button> <button onclick="testSmtp()" class="btn btn-secondary" id="smtpTestBtn"><i class="fas fa-paper-plane" aria-hidden="true"></i> <?= __('btn_test') ?></button>
</div> </div>
<span id="smtpTestResult" style="display:block;margin-top:10px;font-size:13px;"></span> <span id="smtpTestResult" style="display:block;margin-top:10px;font-size:13px;"></span>
</div> </div>
<!-- E-Mail Templates --> <!-- E-Mail Templates -->
<div id="tab-templates_email" class="tab-content"> <div id="tab-templates_email" class="tab-content">
<h2 style="margin-bottom: 20px; color: var(--text-primary);"><i class="fas fa-file-alt"></i> E-Mail Templates</h2> <h2 style="margin-bottom: 20px; color: var(--text-primary);"><i class="fas fa-file-alt" aria-hidden="true"></i> E-Mail Templates</h2>
<form method="post"> <form method="post">
<input type="hidden" name="csrf_token" value="<?= $auth->getCsrfToken() ?>"> <input type="hidden" name="csrf_token" value="<?= $auth->getCsrfToken() ?>">
<input type="hidden" name="form_type" value="templates"> <input type="hidden" name="form_type" value="templates">
@ -652,18 +652,18 @@ $adminBase = '';
<div class="placeholder-info"><h4>Platzhalter:</h4><div class="placeholder-list"><code>{USER_NAME}</code><code>{CHANGES}</code><code>{APP_TITLE}</code><code>{SYSTEM_URL}</code></div></div> <div class="placeholder-info"><h4>Platzhalter:</h4><div class="placeholder-list"><code>{USER_NAME}</code><code>{CHANGES}</code><code>{APP_TITLE}</code><code>{SYSTEM_URL}</code></div></div>
<div class="form-group"><label>Betreff</label><input type="text" name="email_user_notification_subject" value="<?= htmlspecialchars($cs['email_user_notification_subject']) ?>"></div> <div class="form-group"><label>Betreff</label><input type="text" name="email_user_notification_subject" value="<?= htmlspecialchars($cs['email_user_notification_subject']) ?>"></div>
<div class="form-group"><label>E-Mail Text</label><textarea name="email_user_notification_body" class="tinymce-editor"><?= htmlspecialchars($cs['email_user_notification_body']) ?></textarea></div> <div class="form-group"><label>E-Mail Text</label><textarea name="email_user_notification_body" class="tinymce-editor"><?= htmlspecialchars($cs['email_user_notification_body']) ?></textarea></div>
<button type="submit" name="save_settings" class="btn btn-primary"><i class="fas fa-save"></i> <?= __('btn_save') ?></button> <button type="submit" name="save_settings" class="btn btn-primary"><i class="fas fa-save" aria-hidden="true"></i> <?= __('btn_save') ?></button>
</form> </form>
</div> </div>
<!-- System --> <!-- System -->
<div id="tab-system" class="tab-content"> <div id="tab-system" class="tab-content">
<h2 style="margin-bottom: 20px; color: var(--text-primary);"><i class="fas fa-cogs"></i> System & Erweitert</h2> <h2 style="margin-bottom: 20px; color: var(--text-primary);"><i class="fas fa-cogs" aria-hidden="true"></i> System & Erweitert</h2>
<form method="post"> <form method="post">
<input type="hidden" name="csrf_token" value="<?= $auth->getCsrfToken() ?>"> <input type="hidden" name="csrf_token" value="<?= $auth->getCsrfToken() ?>">
<input type="hidden" name="form_type" value="system"> <input type="hidden" name="form_type" value="system">
<div class="info-box"> <div class="info-box">
<h4><i class="fas fa-info-circle"></i> WYSIWYG-Editor</h4> <h4><i class="fas fa-info-circle" aria-hidden="true"></i> WYSIWYG-Editor</h4>
<p><?= __('settings_editor_hint') ?> <code>assets/vendor/</code> <?= __('settings_editor_hint2') ?></p> <p><?= __('settings_editor_hint') ?> <code>assets/vendor/</code> <?= __('settings_editor_hint2') ?></p>
</div> </div>
@ -671,7 +671,7 @@ $adminBase = '';
<h3 style="margin-bottom:15px; color: var(--text-primary);">Druck-Template</h3> <h3 style="margin-bottom:15px; color: var(--text-primary);">Druck-Template</h3>
<div class="placeholder-info"><h4>Platzhalter:</h4><div class="placeholder-list"><code>{VOUCHER_CODE}</code><code>{EXPIRY_DATE}</code><code>{EXPIRY_TIME}</code><code>{SITE_NAME}</code><code>{MAX_USES}</code><code>{APP_TITLE}</code><code>{INSTRUCTIONS}</code></div></div> <div class="placeholder-info"><h4>Platzhalter:</h4><div class="placeholder-list"><code>{VOUCHER_CODE}</code><code>{EXPIRY_DATE}</code><code>{EXPIRY_TIME}</code><code>{SITE_NAME}</code><code>{MAX_USES}</code><code>{APP_TITLE}</code><code>{INSTRUCTIONS}</code></div></div>
<div class="form-group"><label><?= __('settings_print_template') ?></label><textarea name="print_template" class="tinymce-editor" style="min-height:250px;"><?= htmlspecialchars($cs['print_template']) ?></textarea></div> <div class="form-group"><label><?= __('settings_print_template') ?></label><textarea name="print_template" class="tinymce-editor" style="min-height:250px;"><?= htmlspecialchars($cs['print_template']) ?></textarea></div>
<button type="submit" name="save_settings" class="btn btn-primary"><i class="fas fa-save"></i> <?= __('btn_save') ?></button> <button type="submit" name="save_settings" class="btn btn-primary"><i class="fas fa-save" aria-hidden="true"></i> <?= __('btn_save') ?></button>
</form> </form>
<hr class="section-divider"> <hr class="section-divider">
<h3 style="margin-bottom:15px; color: var(--text-primary);">System-Information</h3> <h3 style="margin-bottom:15px; color: var(--text-primary);">System-Information</h3>
@ -684,13 +684,13 @@ $adminBase = '';
<!-- Passwort --> <!-- Passwort -->
<div id="tab-password" class="tab-content"> <div id="tab-password" class="tab-content">
<h2 style="margin-bottom: 20px; color: var(--text-primary);"><i class="fas fa-key"></i> <?= __('settings_tab_password') ?></h2> <h2 style="margin-bottom: 20px; color: var(--text-primary);"><i class="fas fa-key" aria-hidden="true"></i> <?= __('settings_tab_password') ?></h2>
<form method="post" style="max-width:500px;"> <form method="post" style="max-width:500px;">
<input type="hidden" name="csrf_token" value="<?= $auth->getCsrfToken() ?>"> <input type="hidden" name="csrf_token" value="<?= $auth->getCsrfToken() ?>">
<div class="form-group"><label><?= __('settings_pw_current') ?></label><input type="password" name="current_password" required></div> <div class="form-group"><label><?= __('settings_pw_current') ?></label><input type="password" name="current_password" required></div>
<div class="form-group"><label><?= __('settings_pw_new') ?></label><input type="password" name="new_password" required minlength="8"><div class="help-text"><?= __('settings_pw_minlength') ?></div></div> <div class="form-group"><label><?= __('settings_pw_new') ?></label><input type="password" name="new_password" required minlength="8"><div class="help-text"><?= __('settings_pw_minlength') ?></div></div>
<div class="form-group"><label><?= __('settings_pw_confirm') ?></label><input type="password" name="confirm_password" required></div> <div class="form-group"><label><?= __('settings_pw_confirm') ?></label><input type="password" name="confirm_password" required></div>
<button type="submit" name="change_password" class="btn btn-primary"><i class="fas fa-lock"></i> <?= __('settings_tab_password') ?></button> <button type="submit" name="change_password" class="btn btn-primary"><i class="fas fa-lock" aria-hidden="true"></i> <?= __('settings_tab_password') ?></button>
</form> </form>
</div> </div>
</div> </div>
@ -763,7 +763,7 @@ async function testSmtp() {
const result = document.getElementById('smtpTestResult'); const result = document.getElementById('smtpTestResult');
if (!email) { result.textContent = '<?= __('js_enter_email') ?>'; return; } if (!email) { result.textContent = '<?= __('js_enter_email') ?>'; return; }
btn.disabled = true; btn.disabled = true;
btn.innerHTML = '<i class="fas fa-spinner fa-spin"></i>'; btn.innerHTML = '<i class="fas fa-spinner fa-spin" aria-hidden="true"></i>';
const fd = new FormData(); const fd = new FormData();
fd.append('ajax_smtp_test', '1'); fd.append('ajax_smtp_test', '1');
fd.append('csrf_token', '<?= $auth->getCsrfToken() ?>'); fd.append('csrf_token', '<?= $auth->getCsrfToken() ?>');
@ -773,26 +773,26 @@ async function testSmtp() {
result.textContent = data.message; result.textContent = data.message;
result.style.color = data.success ? 'var(--success)' : 'var(--danger)'; result.style.color = data.success ? 'var(--success)' : 'var(--danger)';
btn.disabled = false; btn.disabled = false;
btn.innerHTML = '<i class="fas fa-paper-plane"></i> Testen'; btn.innerHTML = '<i class="fas fa-paper-plane" aria-hidden="true"></i> Testen';
} }
async function testCronJob() { async function testCronJob() {
const btn = document.getElementById('testCronBtn'); const btn = document.getElementById('testCronBtn');
const result = document.getElementById('testCronResult'); const result = document.getElementById('testCronResult');
btn.disabled = true; btn.disabled = true;
btn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> <?= __('js_running') ?>'; btn.innerHTML = '<i class="fas fa-spinner fa-spin" aria-hidden="true"></i> <?= __('js_running') ?>';
try { try {
const res = await fetch('../cron_sync.php?token=<?= htmlspecialchars($cs['cron_token']) ?>'); const res = await fetch('../cron_sync.php?token=<?= htmlspecialchars($cs['cron_token']) ?>');
const data = await res.json(); const data = await res.json();
result.innerHTML = data.success result.innerHTML = data.success
? `<span style="color:var(--success)"><i class="fas fa-check-circle"></i> ${data.message}</span>` ? `<span style="color:var(--success)"><i class="fas fa-check-circle" aria-hidden="true"></i> ${data.message}</span>`
: `<span style="color:var(--danger)"><i class="fas fa-times-circle"></i> ${data.message}</span>`; : `<span style="color:var(--danger)"><i class="fas fa-times-circle" aria-hidden="true"></i> ${data.message}</span>`;
if (data.success) showToast('success', 'Cron ausgeführt', data.message); if (data.success) showToast('success', 'Cron ausgeführt', data.message);
} catch (e) { } catch (e) {
result.innerHTML = `<span style="color:var(--danger)"><?= __('js_error') ?>: ${e.message}</span>`; result.innerHTML = `<span style="color:var(--danger)"><?= __('js_error') ?>: ${e.message}</span>`;
} }
btn.disabled = false; btn.disabled = false;
btn.innerHTML = '<i class="fas fa-play"></i> <?= __('js_run_now') ?>'; btn.innerHTML = '<i class="fas fa-play" aria-hidden="true"></i> <?= __('js_run_now') ?>';
} }
function initTinyMCE() { function initTinyMCE() {

View file

@ -105,20 +105,20 @@ $currentPage = 'sites';
<div class="page-header"> <div class="page-header">
<h1 class="page-title"><?= __('sites_title') ?></h1> <h1 class="page-title"><?= __('sites_title') ?></h1>
<button onclick="openModal()" class="btn btn-primary"> <button onclick="openModal()" class="btn btn-primary">
<i class="fas fa-plus"></i> <?= __('sites_add') ?> <i class="fas fa-plus" aria-hidden="true"></i> <?= __('sites_add') ?>
</button> </button>
</div> </div>
<?php if ($error): ?> <?php if ($error): ?>
<div class="alert alert-error"><i class="fas fa-exclamation-circle"></i> <?= htmlspecialchars($error) ?></div> <div class="alert alert-error"><i class="fas fa-exclamation-circle" aria-hidden="true"></i> <?= htmlspecialchars($error) ?></div>
<?php endif; ?> <?php endif; ?>
<?php if ($success): ?> <?php if ($success): ?>
<div class="alert alert-success"><i class="fas fa-check-circle"></i> <?= htmlspecialchars($success) ?></div> <div class="alert alert-success"><i class="fas fa-check-circle" aria-hidden="true"></i> <?= htmlspecialchars($success) ?></div>
<?php endif; ?> <?php endif; ?>
<?php if (empty($sites)): ?> <?php if (empty($sites)): ?>
<div class="empty-card"> <div class="empty-card">
<i class="fas fa-map-marker-alt" style="font-size:48px;margin-bottom:20px;opacity:.3;display:block;"></i> <i class="fas fa-map-marker-alt" style="font-size:48px;margin-bottom:20px;opacity:.3;display:block;" aria-hidden="true"></i>
<p><?= __('sites_none') ?></p> <p><?= __('sites_none') ?></p>
</div> </div>
<?php else: ?> <?php else: ?>
@ -132,43 +132,43 @@ $currentPage = 'sites';
</div> </div>
<div> <div>
<?php if ($site['is_active']): ?> <?php if ($site['is_active']): ?>
<span class="badge badge-success"><i class="fas fa-check"></i> <?= __('status_active') ?></span> <span class="badge badge-success"><i class="fas fa-check" aria-hidden="true"></i> <?= __('status_active') ?></span>
<?php else: ?> <?php else: ?>
<span class="badge badge-warning"><i class="fas fa-pause"></i> <?= __('status_inactive') ?></span> <span class="badge badge-warning"><i class="fas fa-pause" aria-hidden="true"></i> <?= __('status_inactive') ?></span>
<?php endif; ?> <?php endif; ?>
<?php if ($site['public_access']): ?> <?php if ($site['public_access']): ?>
<span class="badge badge-info"><i class="fas fa-globe"></i> <?= __('status_public') ?></span> <span class="badge badge-info"><i class="fas fa-globe" aria-hidden="true"></i> <?= __('status_public') ?></span>
<?php endif; ?> <?php endif; ?>
</div> </div>
</div> </div>
<div class="site-info"> <div class="site-info">
<div class="site-info-item"> <div class="site-info-item">
<i class="fas fa-server" style="color:var(--accent);width:16px;"></i> <i class="fas fa-server" style="color:var(--accent);width:16px;" aria-hidden="true"></i>
<span style="word-break:break-all;"><?= htmlspecialchars($site['unifi_controller_url']) ?></span> <span style="word-break:break-all;"><?= htmlspecialchars($site['unifi_controller_url']) ?></span>
</div> </div>
<div class="site-info-item"> <div class="site-info-item">
<i class="fas fa-user" style="color:var(--accent);width:16px;"></i> <i class="fas fa-user" style="color:var(--accent);width:16px;" aria-hidden="true"></i>
<span><?= htmlspecialchars($site['unifi_username']) ?></span> <span><?= htmlspecialchars($site['unifi_username']) ?></span>
</div> </div>
<div class="site-info-item"> <div class="site-info-item">
<i class="fas fa-clock" style="color:var(--text-muted);width:16px;"></i> <i class="fas fa-clock" style="color:var(--text-muted);width:16px;" aria-hidden="true"></i>
<span style="color:var(--text-muted);"><?= date('d.m.Y', strtotime($site['created_at'])) ?></span> <span style="color:var(--text-muted);"><?= date('d.m.Y', strtotime($site['created_at'])) ?></span>
</div> </div>
</div> </div>
<div class="site-actions"> <div class="site-actions">
<button onclick="openEditModal(<?= $site['id'] ?>, '<?= htmlspecialchars($site['name'], ENT_QUOTES) ?>', '<?= htmlspecialchars($site['site_id'], ENT_QUOTES) ?>', '<?= htmlspecialchars($site['unifi_controller_url'], ENT_QUOTES) ?>', '<?= htmlspecialchars($site['unifi_username'], ENT_QUOTES) ?>', <?= $site['public_access'] ?>)" <button onclick="openEditModal(<?= $site['id'] ?>, '<?= htmlspecialchars($site['name'], ENT_QUOTES) ?>', '<?= htmlspecialchars($site['site_id'], ENT_QUOTES) ?>', '<?= htmlspecialchars($site['unifi_controller_url'], ENT_QUOTES) ?>', '<?= htmlspecialchars($site['unifi_username'], ENT_QUOTES) ?>', <?= $site['public_access'] ?>)"
class="btn btn-secondary btn-sm"> class="btn btn-secondary btn-sm">
<i class="fas fa-edit"></i> <?= __('btn_edit') ?> <i class="fas fa-edit" aria-hidden="true"></i> <?= __('btn_edit') ?>
</button> </button>
<a href="?toggle=<?= $site['id'] ?>&token=<?= $auth->getCsrfToken() ?>" <a href="?toggle=<?= $site['id'] ?>&token=<?= $auth->getCsrfToken() ?>"
class="btn btn-secondary btn-sm"> class="btn btn-secondary btn-sm">
<i class="fas fa-<?= $site['is_active'] ? 'pause' : 'play' ?>"></i> <i class="fas fa-<?= $site['is_active'] ? 'pause' : 'play' ?>" aria-hidden="true"></i>
<?= $site['is_active'] ? __('sites_deactivate') : __('sites_activate') ?> <?= $site['is_active'] ? __('sites_deactivate') : __('sites_activate') ?>
</a> </a>
<a href="?delete=<?= $site['id'] ?>&token=<?= $auth->getCsrfToken() ?>" <a href="?delete=<?= $site['id'] ?>&token=<?= $auth->getCsrfToken() ?>"
class="btn btn-danger-soft btn-sm" class="btn btn-danger-soft btn-sm"
onclick="return confirm('<?= __('js_confirm_delete_site') ?>')"> onclick="return confirm('<?= __('js_confirm_delete_site') ?>')">
<i class="fas fa-trash"></i> <i class="fas fa-trash" aria-hidden="true"></i>
</a> </a>
</div> </div>
</div> </div>
@ -219,7 +219,7 @@ $currentPage = 'sites';
</div> </div>
<div style="display:flex;gap:10px;margin-top:20px;"> <div style="display:flex;gap:10px;margin-top:20px;">
<button type="submit" class="btn btn-primary" style="flex:1;" id="addSiteSubmitBtn"> <button type="submit" class="btn btn-primary" style="flex:1;" id="addSiteSubmitBtn">
<i class="fas fa-save"></i> <?= __('sites_add') ?> <i class="fas fa-save" aria-hidden="true"></i> <?= __('sites_add') ?>
</button> </button>
<button type="button" onclick="closeModal('addSiteModal')" class="btn btn-secondary"><?= __('btn_cancel') ?></button> <button type="button" onclick="closeModal('addSiteModal')" class="btn btn-secondary"><?= __('btn_cancel') ?></button>
</div> </div>
@ -269,7 +269,7 @@ $currentPage = 'sites';
</div> </div>
<div style="display:flex;gap:10px;margin-top:20px;"> <div style="display:flex;gap:10px;margin-top:20px;">
<button type="submit" class="btn btn-primary" style="flex:1;" id="editSiteSubmitBtn"> <button type="submit" class="btn btn-primary" style="flex:1;" id="editSiteSubmitBtn">
<i class="fas fa-save"></i> <?= __('btn_save') ?> <i class="fas fa-save" aria-hidden="true"></i> <?= __('btn_save') ?>
</button> </button>
<button type="button" onclick="closeModal('editSiteModal')" class="btn btn-secondary"><?= __('btn_cancel') ?></button> <button type="button" onclick="closeModal('editSiteModal')" class="btn btn-secondary"><?= __('btn_cancel') ?></button>
</div> </div>
@ -278,7 +278,7 @@ $currentPage = 'sites';
</div> </div>
</div> </div>
<div id="toast-container"></div> <div id="toast-container" role="status" aria-live="polite"></div>
<script src="../assets/global.js"></script> <script src="../assets/global.js"></script>
<script> <script>
function openModal() { document.getElementById('addSiteModal').classList.add('active'); } function openModal() { document.getElementById('addSiteModal').classList.add('active'); }
@ -298,12 +298,12 @@ function openEditModal(id, name, siteIdStr, controllerUrl, username, publicAcces
document.getElementById('addSiteForm').addEventListener('submit', function() { document.getElementById('addSiteForm').addEventListener('submit', function() {
const btn = document.getElementById('addSiteSubmitBtn'); const btn = document.getElementById('addSiteSubmitBtn');
btn.disabled = true; btn.disabled = true;
btn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> <?= addslashes(__('sites_testing')) ?>'; btn.innerHTML = '<i class="fas fa-spinner fa-spin" aria-hidden="true"></i> <?= addslashes(__('sites_testing')) ?>';
}); });
document.getElementById('editSiteForm').addEventListener('submit', function() { document.getElementById('editSiteForm').addEventListener('submit', function() {
const btn = document.getElementById('editSiteSubmitBtn'); const btn = document.getElementById('editSiteSubmitBtn');
btn.disabled = true; btn.disabled = true;
btn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> <?= addslashes(__('sites_testing')) ?>'; btn.innerHTML = '<i class="fas fa-spinner fa-spin" aria-hidden="true"></i> <?= addslashes(__('sites_testing')) ?>';
}); });
['addSiteModal','editSiteModal'].forEach(id => { ['addSiteModal','editSiteModal'].forEach(id => {

View file

@ -107,15 +107,15 @@ $adminBase = '';
<p class="page-subtitle"><?= __('templates_subtitle') ?></p> <p class="page-subtitle"><?= __('templates_subtitle') ?></p>
</div> </div>
<button onclick="openAddModal()" class="btn btn-primary"> <button onclick="openAddModal()" class="btn btn-primary">
<i class="fas fa-plus"></i> <?= __('templates_add') ?> <i class="fas fa-plus" aria-hidden="true"></i> <?= __('templates_add') ?>
</button> </button>
</div> </div>
<?php if ($error): ?> <?php if ($error): ?>
<div class="alert alert-error"><i class="fas fa-exclamation-circle"></i> <?= htmlspecialchars($error) ?></div> <div class="alert alert-error"><i class="fas fa-exclamation-circle" aria-hidden="true"></i> <?= htmlspecialchars($error) ?></div>
<?php endif; ?> <?php endif; ?>
<?php if ($success): ?> <?php if ($success): ?>
<div class="alert alert-success"><i class="fas fa-check-circle"></i> <?= htmlspecialchars($success) ?></div> <div class="alert alert-success"><i class="fas fa-check-circle" aria-hidden="true"></i> <?= htmlspecialchars($success) ?></div>
<?php endif; ?> <?php endif; ?>
<div class="card"> <div class="card">
@ -125,15 +125,15 @@ $adminBase = '';
</div> </div>
<?php if (empty($templates)): ?> <?php if (empty($templates)): ?>
<div class="empty-state"> <div class="empty-state">
<i class="fas fa-layer-group"></i> <i class="fas fa-layer-group" aria-hidden="true"></i>
<p style="font-size: 15px; margin-bottom: 8px;"><?= __('templates_none') ?></p> <p style="font-size: 15px; margin-bottom: 8px;"><?= __('templates_none') ?></p>
<p style="font-size: 13px;"><?= __('templates_add_hint') ?></p> <p style="font-size: 13px;"><?= __('templates_add_hint') ?></p>
<button onclick="openAddModal()" class="btn btn-primary" style="margin-top: 20px;"><i class="fas fa-plus"></i> <?= __('templates_add') ?></button> <button onclick="openAddModal()" class="btn btn-primary" style="margin-top: 20px;"><i class="fas fa-plus" aria-hidden="true"></i> <?= __('templates_add') ?></button>
</div> </div>
<?php else: ?> <?php else: ?>
<div style="overflow-x: auto;"> <div style="overflow-x: auto;">
<div class="table-container"> <div class="table-container">
<table class="table"> <table class="table table-stack">
<thead> <thead>
<tr> <tr>
<th><?= __('templates_name') ?></th> <th><?= __('templates_name') ?></th>
@ -147,11 +147,11 @@ $adminBase = '';
<tbody> <tbody>
<?php foreach ($templates as $t): ?> <?php foreach ($templates as $t): ?>
<tr> <tr>
<td><strong><?= htmlspecialchars($t['name']) ?></strong></td> <td data-label="<?= __('templates_name') ?>"><strong><?= htmlspecialchars($t['name']) ?></strong></td>
<td> <td data-label="<?= __('templates_devices') ?>">
<span class="duration-badge"><i class="fas fa-mobile-alt"></i> <?= (int)$t['max_uses'] ?></span> <span class="duration-badge"><i class="fas fa-mobile-alt" aria-hidden="true"></i> <?= (int)$t['max_uses'] ?></span>
</td> </td>
<td> <td data-label="<?= __('templates_duration') ?>">
<?php <?php
$m = (int)$t['expire_minutes']; $m = (int)$t['expire_minutes'];
if ($m >= 1440 && $m % 1440 === 0) { if ($m >= 1440 && $m % 1440 === 0) {
@ -162,22 +162,22 @@ $adminBase = '';
$durLabel = $m . ' Min.'; $durLabel = $m . ' Min.';
} }
?> ?>
<span class="duration-badge"><i class="fas fa-clock"></i> <?= $durLabel ?></span> <span class="duration-badge"><i class="fas fa-clock" aria-hidden="true"></i> <?= $durLabel ?></span>
</td> </td>
<td style="color: var(--text-secondary);"><?= htmlspecialchars($t['description'] ?? '-') ?></td> <td data-label="<?= __('templates_desc') ?>" style="color: var(--text-secondary);"><?= htmlspecialchars($t['description'] ?? '-') ?></td>
<td> <td data-label="<?= __('label_status') ?>">
<?php if ($t['is_active']): ?> <?php if ($t['is_active']): ?>
<span class="badge badge-success"><?= __('status_active') ?></span> <span class="badge badge-success"><?= __('status_active') ?></span>
<?php else: ?> <?php else: ?>
<span class="badge badge-secondary"><?= __('status_inactive') ?></span> <span class="badge badge-secondary"><?= __('status_inactive') ?></span>
<?php endif; ?> <?php endif; ?>
</td> </td>
<td> <td data-label="<?= __('label_actions') ?>">
<button onclick="openEditModal(<?= $t['id'] ?>, '<?= htmlspecialchars($t['name'], ENT_QUOTES) ?>', <?= (int)$t['max_uses'] ?>, <?= (int)$t['expire_minutes'] ?>, '<?= htmlspecialchars($t['description'] ?? '', ENT_QUOTES) ?>', <?= (int)$t['is_active'] ?>, <?= (int)($t['qos_rate_max_down'] ?? 0) ?>, <?= (int)($t['qos_rate_max_up'] ?? 0) ?>, <?= (int)($t['qos_usage_quota'] ?? 0) ?>)" <button onclick="openEditModal(<?= $t['id'] ?>, '<?= htmlspecialchars($t['name'], ENT_QUOTES) ?>', <?= (int)$t['max_uses'] ?>, <?= (int)$t['expire_minutes'] ?>, '<?= htmlspecialchars($t['description'] ?? '', ENT_QUOTES) ?>', <?= (int)$t['is_active'] ?>, <?= (int)($t['qos_rate_max_down'] ?? 0) ?>, <?= (int)($t['qos_rate_max_up'] ?? 0) ?>, <?= (int)($t['qos_usage_quota'] ?? 0) ?>)"
class="btn btn-secondary btn-small"><i class="fas fa-edit"></i></button> class="btn btn-secondary btn-small"><i class="fas fa-edit" aria-hidden="true"></i></button>
<a href="?delete=<?= $t['id'] ?>&token=<?= $auth->getCsrfToken() ?>" <a href="?delete=<?= $t['id'] ?>&token=<?= $auth->getCsrfToken() ?>"
onclick="return confirm('<?= __('js_confirm_delete_template') ?>')" onclick="return confirm('<?= __('js_confirm_delete_template') ?>')"
class="btn btn-danger-soft btn-small"><i class="fas fa-trash"></i></a> class="btn btn-danger-soft btn-small"><i class="fas fa-trash" aria-hidden="true"></i></a>
</td> </td>
</tr> </tr>
<?php endforeach; ?> <?php endforeach; ?>
@ -194,7 +194,7 @@ $adminBase = '';
<div id="addModal" class="modal"> <div id="addModal" class="modal">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<h2 class="modal-title"><i class="fas fa-plus-circle" style="color: var(--accent);"></i> <?= __('templates_add') ?></h2> <h2 class="modal-title"><i class="fas fa-plus-circle" style="color: var(--accent);" aria-hidden="true"></i> <?= __('templates_add') ?></h2>
<button class="modal-close" onclick="closeModal('addModal')">&times;</button> <button class="modal-close" onclick="closeModal('addModal')">&times;</button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
@ -220,7 +220,7 @@ $adminBase = '';
<div class="form-group"><label>Datenlimit (MB)</label><input type="number" name="qos_usage_quota" min="0" placeholder="0 = unbegrenzt"></div> <div class="form-group"><label>Datenlimit (MB)</label><input type="number" name="qos_usage_quota" min="0" placeholder="0 = unbegrenzt"></div>
</div> </div>
<div style="display:flex;gap:10px;margin-top:20px;"> <div style="display:flex;gap:10px;margin-top:20px;">
<button type="submit" name="add_template" class="btn btn-primary" style="flex:1;"><i class="fas fa-save"></i> <?= __('btn_save') ?></button> <button type="submit" name="add_template" class="btn btn-primary" style="flex:1;"><i class="fas fa-save" aria-hidden="true"></i> <?= __('btn_save') ?></button>
<button type="button" onclick="closeModal('addModal')" class="btn btn-secondary"><?= __('btn_cancel') ?></button> <button type="button" onclick="closeModal('addModal')" class="btn btn-secondary"><?= __('btn_cancel') ?></button>
</div> </div>
</form> </form>
@ -232,7 +232,7 @@ $adminBase = '';
<div id="editModal" class="modal"> <div id="editModal" class="modal">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<h2 class="modal-title"><i class="fas fa-edit" style="color: var(--accent);"></i> <?= __('templates_edit') ?></h2> <h2 class="modal-title"><i class="fas fa-edit" style="color: var(--accent);" aria-hidden="true"></i> <?= __('templates_edit') ?></h2>
<button class="modal-close" onclick="closeModal('editModal')">&times;</button> <button class="modal-close" onclick="closeModal('editModal')">&times;</button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
@ -261,7 +261,7 @@ $adminBase = '';
<label for="editActive" style="margin:0;"><?= __('status_active') ?></label> <label for="editActive" style="margin:0;"><?= __('status_active') ?></label>
</div> </div>
<div style="display:flex;gap:10px;"> <div style="display:flex;gap:10px;">
<button type="submit" name="edit_template" class="btn btn-primary" style="flex:1;"><i class="fas fa-save"></i> <?= __('btn_save') ?></button> <button type="submit" name="edit_template" class="btn btn-primary" style="flex:1;"><i class="fas fa-save" aria-hidden="true"></i> <?= __('btn_save') ?></button>
<button type="button" onclick="closeModal('editModal')" class="btn btn-secondary"><?= __('btn_cancel') ?></button> <button type="button" onclick="closeModal('editModal')" class="btn btn-secondary"><?= __('btn_cancel') ?></button>
</div> </div>
</form> </form>

View file

@ -175,26 +175,26 @@ $currentPage = 'users';
<div class="page-header"> <div class="page-header">
<h1 class="page-title"><?= __('users_title') ?></h1> <h1 class="page-title"><?= __('users_title') ?></h1>
<button onclick="openModal()" class="btn btn-primary"> <button onclick="openModal()" class="btn btn-primary">
<i class="fas fa-plus"></i> <?= __('users_add') ?> <i class="fas fa-plus" aria-hidden="true"></i> <?= __('users_add') ?>
</button> </button>
</div> </div>
<?php if ($error): ?> <?php if ($error): ?>
<div class="alert alert-error"><i class="fas fa-exclamation-circle"></i> <?= htmlspecialchars($error) ?></div> <div class="alert alert-error"><i class="fas fa-exclamation-circle" aria-hidden="true"></i> <?= htmlspecialchars($error) ?></div>
<?php endif; ?> <?php endif; ?>
<?php if ($success): ?> <?php if ($success): ?>
<div class="alert alert-success"><i class="fas fa-check-circle"></i> <?= htmlspecialchars($success) ?></div> <div class="alert alert-success"><i class="fas fa-check-circle" aria-hidden="true"></i> <?= htmlspecialchars($success) ?></div>
<?php endif; ?> <?php endif; ?>
<div class="card"> <div class="card">
<div class="card-header"><h2 class="card-title"><?= __('users_all') ?></h2></div> <div class="card-header"><h2 class="card-title"><?= __('users_all') ?></h2></div>
<div class="card-body" style="padding:0;"> <div class="card-body" style="padding:0;">
<?php if (empty($users)): ?> <?php if (empty($users)): ?>
<div class="empty-state"><i class="fas fa-users"></i><p><?= __('users_none_found') ?></p></div> <div class="empty-state"><i class="fas fa-users" aria-hidden="true"></i><p><?= __('users_none_found') ?></p></div>
<?php else: ?> <?php else: ?>
<div style="overflow-x:auto;"> <div style="overflow-x:auto;">
<div class="table-container"> <div class="table-container">
<table class="table"> <table class="table table-stack">
<thead> <thead>
<tr> <tr>
<th><?= __('label_name') ?></th> <th><?= __('label_name') ?></th>
@ -210,28 +210,28 @@ $currentPage = 'users';
<?php foreach ($users as $user): ?> <?php foreach ($users as $user): ?>
<?php $userSiteIds = array_column($userSiteAccess[$user['id']]??[], 'id'); ?> <?php $userSiteIds = array_column($userSiteAccess[$user['id']]??[], 'id'); ?>
<tr> <tr>
<td> <td data-label="<?= __('label_name') ?>">
<strong><?= htmlspecialchars($user['name']) ?></strong> <strong><?= htmlspecialchars($user['name']) ?></strong>
<?php if ($user['id'] == $_SESSION['user_id']): ?> <?php if ($user['id'] == $_SESSION['user_id']): ?>
<span class="badge badge-info"><?= __('users_you') ?></span> <span class="badge badge-info"><?= __('users_you') ?></span>
<?php endif; ?> <?php endif; ?>
</td> </td>
<td><?= htmlspecialchars($user['email']) ?></td> <td data-label="<?= __('label_email') ?>"><?= htmlspecialchars($user['email']) ?></td>
<td> <td data-label="<?= __('label_role') ?>">
<?php if ($user['is_admin']): ?> <?php if ($user['is_admin']): ?>
<span class="badge badge-danger"><i class="fas fa-crown"></i> <?= __('status_admin') ?></span> <span class="badge badge-danger"><i class="fas fa-crown" aria-hidden="true"></i> <?= __('status_admin') ?></span>
<?php else: ?> <?php else: ?>
<span class="badge badge-info"><?= __('status_user') ?></span> <span class="badge badge-info"><?= __('status_user') ?></span>
<?php endif; ?> <?php endif; ?>
</td> </td>
<td> <td data-label="<?= __('label_status') ?>">
<?php if ($user['is_active']): ?> <?php if ($user['is_active']): ?>
<span class="badge badge-success"><i class="fas fa-check"></i> <?= __('status_active') ?></span> <span class="badge badge-success"><i class="fas fa-check" aria-hidden="true"></i> <?= __('status_active') ?></span>
<?php else: ?> <?php else: ?>
<span class="badge badge-warning"><i class="fas fa-pause"></i> <?= __('status_inactive') ?></span> <span class="badge badge-warning"><i class="fas fa-pause" aria-hidden="true"></i> <?= __('status_inactive') ?></span>
<?php endif; ?> <?php endif; ?>
</td> </td>
<td> <td data-label="<?= __('users_site_access') ?>">
<?php if ($user['is_admin']): ?> <?php if ($user['is_admin']): ?>
<em style="color:var(--text-muted);"><?= __('users_all_sites') ?></em> <em style="color:var(--text-muted);"><?= __('users_all_sites') ?></em>
<?php elseif (!empty($userSiteAccess[$user['id']])): ?> <?php elseif (!empty($userSiteAccess[$user['id']])): ?>
@ -242,7 +242,7 @@ $currentPage = 'users';
<em style="color:var(--text-muted);"><?= __('users_none') ?></em> <em style="color:var(--text-muted);"><?= __('users_none') ?></em>
<?php endif; ?> <?php endif; ?>
</td> </td>
<td style="font-size:13px;"> <td data-label="<?= __('users_last_login') ?>" style="font-size:13px;">
<?php if ($user['last_login']): ?> <?php if ($user['last_login']): ?>
<?= date('d.m.Y H:i', strtotime($user['last_login'])) ?> <?= date('d.m.Y H:i', strtotime($user['last_login'])) ?>
<?php else: ?> <?php else: ?>
@ -253,31 +253,31 @@ $currentPage = 'users';
<div class="action-btns"> <div class="action-btns">
<button onclick="openEditModal(<?= $user['id'] ?>, '<?= htmlspecialchars($user['name'], ENT_QUOTES) ?>', <?= $user['is_admin'] ?>, [<?= implode(',', array_map('intval', $userSiteIds)) ?>])" <button onclick="openEditModal(<?= $user['id'] ?>, '<?= htmlspecialchars($user['name'], ENT_QUOTES) ?>', <?= $user['is_admin'] ?>, [<?= implode(',', array_map('intval', $userSiteIds)) ?>])"
class="btn btn-secondary btn-sm" title="<?= __('btn_edit') ?>"> class="btn btn-secondary btn-sm" title="<?= __('btn_edit') ?>">
<i class="fas fa-edit"></i> <i class="fas fa-edit" aria-hidden="true"></i>
</button> </button>
<?php if ($user['id'] != $_SESSION['user_id']): ?> <?php if ($user['id'] != $_SESSION['user_id']): ?>
<a href="?toggle=<?= $user['id'] ?>&token=<?= $auth->getCsrfToken() ?>" <a href="?toggle=<?= $user['id'] ?>&token=<?= $auth->getCsrfToken() ?>"
class="btn btn-secondary btn-sm" title="<?= $user['is_active'] ? __('sites_deactivate') : __('sites_activate') ?>"> class="btn btn-secondary btn-sm" title="<?= $user['is_active'] ? __('sites_deactivate') : __('sites_activate') ?>">
<i class="fas fa-<?= $user['is_active'] ? 'pause' : 'play' ?>"></i> <i class="fas fa-<?= $user['is_active'] ? 'pause' : 'play' ?>" aria-hidden="true"></i>
</a> </a>
<?php if ($smtpEnabled && !empty($user['password_hash'])): ?> <?php if ($smtpEnabled && !empty($user['password_hash'])): ?>
<a href="?send_reset=<?= $user['id'] ?>&token=<?= $auth->getCsrfToken() ?>" <a href="?send_reset=<?= $user['id'] ?>&token=<?= $auth->getCsrfToken() ?>"
class="btn btn-warning btn-sm" title="<?= __('users_reset_pw') ?>" class="btn btn-warning btn-sm" title="<?= __('users_reset_pw') ?>"
onclick="return confirm('Passwort-Reset-Link senden an <?= htmlspecialchars($user['email'], ENT_QUOTES) ?>?')"> onclick="return confirm('Passwort-Reset-Link senden an <?= htmlspecialchars($user['email'], ENT_QUOTES) ?>?')">
<i class="fas fa-key"></i> <i class="fas fa-key" aria-hidden="true"></i>
</a> </a>
<?php endif; ?> <?php endif; ?>
<?php if (!empty($user['totp_enabled'])): ?> <?php if (!empty($user['totp_enabled'])): ?>
<a href="?reset_2fa=<?= $user['id'] ?>&token=<?= $auth->getCsrfToken() ?>" <a href="?reset_2fa=<?= $user['id'] ?>&token=<?= $auth->getCsrfToken() ?>"
class="btn btn-secondary btn-sm" title="2FA zurücksetzen" class="btn btn-secondary btn-sm" title="2FA zurücksetzen"
onclick="return confirm('2FA für <?= htmlspecialchars($user['email'], ENT_QUOTES) ?> zurücksetzen?')"> onclick="return confirm('2FA für <?= htmlspecialchars($user['email'], ENT_QUOTES) ?> zurücksetzen?')">
<i class="fas fa-user-shield"></i> <i class="fas fa-user-shield" aria-hidden="true"></i>
</a> </a>
<?php endif; ?> <?php endif; ?>
<a href="?delete=<?= $user['id'] ?>&token=<?= $auth->getCsrfToken() ?>" <a href="?delete=<?= $user['id'] ?>&token=<?= $auth->getCsrfToken() ?>"
class="btn btn-danger-soft btn-sm" title="<?= __('btn_delete') ?>" class="btn btn-danger-soft btn-sm" title="<?= __('btn_delete') ?>"
onclick="return confirm('<?= __('js_confirm_delete_user') ?>')"> onclick="return confirm('<?= __('js_confirm_delete_user') ?>')">
<i class="fas fa-trash"></i> <i class="fas fa-trash" aria-hidden="true"></i>
</a> </a>
<?php endif; ?> <?php endif; ?>
</div> </div>
@ -342,7 +342,7 @@ $currentPage = 'users';
</div> </div>
<div style="display:flex;gap:10px;margin-top:20px;"> <div style="display:flex;gap:10px;margin-top:20px;">
<button type="submit" name="add_user" class="btn btn-primary" style="flex:1;"> <button type="submit" name="add_user" class="btn btn-primary" style="flex:1;">
<i class="fas fa-save"></i> <?= __('users_save') ?> <i class="fas fa-save" aria-hidden="true"></i> <?= __('users_save') ?>
</button> </button>
<button type="button" onclick="closeModal('addUserModal')" class="btn btn-secondary"><?= __('btn_cancel') ?></button> <button type="button" onclick="closeModal('addUserModal')" class="btn btn-secondary"><?= __('btn_cancel') ?></button>
</div> </div>
@ -386,7 +386,7 @@ $currentPage = 'users';
</div> </div>
<div style="display:flex;gap:10px;margin-top:20px;"> <div style="display:flex;gap:10px;margin-top:20px;">
<button type="submit" name="edit_user" class="btn btn-primary" style="flex:1;"> <button type="submit" name="edit_user" class="btn btn-primary" style="flex:1;">
<i class="fas fa-save"></i> <?= __('users_save_edit') ?> <i class="fas fa-save" aria-hidden="true"></i> <?= __('users_save_edit') ?>
</button> </button>
<button type="button" onclick="closeModal('editUserModal')" class="btn btn-secondary"><?= __('btn_cancel') ?></button> <button type="button" onclick="closeModal('editUserModal')" class="btn btn-secondary"><?= __('btn_cancel') ?></button>
</div> </div>
@ -395,7 +395,7 @@ $currentPage = 'users';
</div> </div>
</div> </div>
<div id="toast-container"></div> <div id="toast-container" role="status" aria-live="polite"></div>
<script src="../assets/global.js"></script> <script src="../assets/global.js"></script>
<script> <script>
function openModal() { document.getElementById('addUserModal').classList.add('active'); } function openModal() { document.getElementById('addUserModal').classList.add('active'); }

View file

@ -143,9 +143,9 @@ $currentPage = 'vouchers';
<?php if (empty($sites)): ?> <?php if (empty($sites)): ?>
<div class="no-sites-warning"> <div class="no-sites-warning">
<i class="fas fa-triangle-exclamation" style="font-size:26px;margin-bottom:12px;display:block;"></i> <i class="fas fa-triangle-exclamation" style="font-size:26px;margin-bottom:12px;display:block;" aria-hidden="true"></i>
<h3><?= __('vouchers_no_sites') ?></h3> <h3><?= __('vouchers_no_sites') ?></h3>
<a href="sites.php" class="btn btn-primary" style="margin-top:20px;"><i class="fas fa-plus"></i> <?= __('nav_sites') ?></a> <a href="sites.php" class="btn btn-primary" style="margin-top:20px;"><i class="fas fa-plus" aria-hidden="true"></i> <?= __('nav_sites') ?></a>
</div> </div>
<?php else: ?> <?php else: ?>
@ -168,7 +168,7 @@ $currentPage = 'vouchers';
</select> </select>
<input type="search" id="searchInput" class="search-bar" placeholder="<?= __('vouchers_search') ?>" oninput="filterAndRender()" style="display:none;"> <input type="search" id="searchInput" class="search-bar" placeholder="<?= __('vouchers_search') ?>" oninput="filterAndRender()" style="display:none;">
<button id="refreshBtn" class="btn btn-secondary" onclick="loadVouchers(true)" disabled> <button id="refreshBtn" class="btn btn-secondary" onclick="loadVouchers(true)" disabled>
<i class="fas fa-sync-alt"></i> <?= __('btn_refresh') ?> <i class="fas fa-sync-alt" aria-hidden="true"></i> <?= __('btn_refresh') ?>
</button> </button>
</div> </div>
</div> </div>
@ -187,12 +187,12 @@ $currentPage = 'vouchers';
<div class="card-header"> <div class="card-header">
<h2 class="card-title" id="voucherListTitle">Vouchers</h2> <h2 class="card-title" id="voucherListTitle">Vouchers</h2>
<a id="csvExportBtn" style="display:none;" class="btn btn-secondary btn-sm" href="#"> <a id="csvExportBtn" style="display:none;" class="btn btn-secondary btn-sm" href="#">
<i class="fas fa-download"></i> <?= __('btn_export_csv') ?> <i class="fas fa-download" aria-hidden="true"></i> <?= __('btn_export_csv') ?>
</a> </a>
</div> </div>
<div class="card-body" style="padding:0;"> <div class="card-body" style="padding:0;">
<div id="voucherContent"> <div id="voucherContent">
<div class="empty-state"><i class="fas fa-ticket-alt"></i><p><?= __('vouchers_select_hint') ?></p></div> <div class="empty-state"><i class="fas fa-ticket-alt" aria-hidden="true"></i><p><?= __('vouchers_select_hint') ?></p></div>
</div> </div>
</div> </div>
</div> </div>
@ -200,10 +200,12 @@ $currentPage = 'vouchers';
</main> </main>
<div id="toast-container"></div> <div id="toast-container" role="status" aria-live="polite"></div>
<script src="../assets/global.js"></script> <script src="../assets/global.js"></script>
<script> <script>
const csrfToken = '<?= $auth->getCsrfToken() ?>'; const csrfToken = '<?= $auth->getCsrfToken() ?>';
// Datums- und Zeitformat folgen der gewaehlten Sprache
const LOCALE = '<?= I18n::getLanguage() === 'en' ? 'en-GB' : 'de-DE' ?>';
let currentSiteId = null; let currentSiteId = null;
let allVouchers = []; let allVouchers = [];
let currentFilter = 'all'; let currentFilter = 'all';
@ -218,7 +220,7 @@ async function loadVouchers(syncFirst=false) {
const searchInput = document.getElementById('searchInput'); const searchInput = document.getElementById('searchInput');
if (!siteId) { if (!siteId) {
document.getElementById('voucherContent').innerHTML = `<div class="empty-state"><i class="fas fa-ticket-alt"></i><p><?= addslashes(__('vouchers_select_hint')) ?></p></div>`; document.getElementById('voucherContent').innerHTML = `<div class="empty-state"><i class="fas fa-ticket-alt" aria-hidden="true"></i><p><?= addslashes(__('vouchers_select_hint')) ?></p></div>`;
document.getElementById('statsContainer').style.display = 'none'; document.getElementById('statsContainer').style.display = 'none';
searchInput.style.display = 'none'; searchInput.style.display = 'none';
refreshBtn.disabled = true; refreshBtn.disabled = true;
@ -227,9 +229,9 @@ async function loadVouchers(syncFirst=false) {
currentSiteId = siteId; currentSiteId = siteId;
refreshBtn.disabled = false; refreshBtn.disabled = false;
refreshBtn.innerHTML = `<i class="fas fa-spinner fa-spin"></i> ${syncFirst ? '<?= addslashes(__('btn_refresh')) ?>' : '<?= addslashes(__('btn_refresh')) ?>'}`; refreshBtn.innerHTML = `<i class="fas fa-spinner fa-spin" aria-hidden="true"></i> ${syncFirst ? '<?= addslashes(__('btn_refresh')) ?>' : '<?= addslashes(__('btn_refresh')) ?>'}`;
document.getElementById('voucherContent').innerHTML = `<div class="loading"><i class="fas fa-spinner"></i><span>${syncFirst ? 'Synchronisiere...' : 'Lade...'}</span></div>`; document.getElementById('voucherContent').innerHTML = `<div class="loading"><i class="fas fa-spinner" aria-hidden="true"></i><span>${syncFirst ? 'Synchronisiere...' : 'Lade...'}</span></div>`;
try { try {
const result = await fetch(`vouchers.php?ajax_get_vouchers=1&site_id=${siteId}${syncFirst?'&sync=1':''}`).then(r=>r.json()); const result = await fetch(`vouchers.php?ajax_get_vouchers=1&site_id=${siteId}${syncFirst?'&sync=1':''}`).then(r=>r.json());
@ -247,15 +249,15 @@ async function loadVouchers(syncFirst=false) {
csvBtn.href = `vouchers.php?export_csv=1&site_id=${siteId}&token=${csrfToken}`; csvBtn.href = `vouchers.php?export_csv=1&site_id=${siteId}&token=${csrfToken}`;
if (syncFirst) showToast('success', '<?= addslashes(__('btn_refresh')) ?>', `${result.count} Vouchers geladen`); if (syncFirst) showToast('success', '<?= addslashes(__('btn_refresh')) ?>', `${result.count} Vouchers geladen`);
} else { } else {
document.getElementById('voucherContent').innerHTML = `<div class="empty-state"><i class="fas fa-exclamation-circle" style="color:var(--danger)"></i><p>${result.message}</p></div>`; document.getElementById('voucherContent').innerHTML = `<div class="empty-state"><i class="fas fa-exclamation-circle" style="color:var(--danger)" aria-hidden="true"></i><p>${result.message}</p></div>`;
document.getElementById('statsContainer').style.display = 'none'; document.getElementById('statsContainer').style.display = 'none';
} }
} catch(e) { } catch(e) {
document.getElementById('voucherContent').innerHTML = `<div class="empty-state"><i class="fas fa-exclamation-circle" style="color:var(--danger)"></i><p>Verbindungsfehler: ${e.message}</p></div>`; document.getElementById('voucherContent').innerHTML = `<div class="empty-state"><i class="fas fa-exclamation-circle" style="color:var(--danger)" aria-hidden="true"></i><p>Verbindungsfehler: ${e.message}</p></div>`;
document.getElementById('statsContainer').style.display = 'none'; document.getElementById('statsContainer').style.display = 'none';
} }
refreshBtn.disabled = false; refreshBtn.disabled = false;
refreshBtn.innerHTML = '<i class="fas fa-sync-alt"></i> <?= addslashes(__('btn_refresh')) ?>'; refreshBtn.innerHTML = '<i class="fas fa-sync-alt" aria-hidden="true"></i> <?= addslashes(__('btn_refresh')) ?>';
} }
function updateStats() { function updateStats() {
@ -293,7 +295,7 @@ function renderVouchers() {
const pageVouchers = vouchers.slice((currentPage-1)*PAGE_SIZE, currentPage*PAGE_SIZE); const pageVouchers = vouchers.slice((currentPage-1)*PAGE_SIZE, currentPage*PAGE_SIZE);
if (vouchers.length === 0) { if (vouchers.length === 0) {
document.getElementById('voucherContent').innerHTML = `<div class="empty-state"><i class="fas fa-ticket-alt"></i><p><?= addslashes(__('vouchers_none')) ?></p></div>`; document.getElementById('voucherContent').innerHTML = `<div class="empty-state"><i class="fas fa-ticket-alt" aria-hidden="true"></i><p><?= addslashes(__('vouchers_none')) ?></p></div>`;
return; return;
} }
@ -309,7 +311,7 @@ function renderVouchers() {
<button class="filter-btn ${currentFilter==='expired'?'active':''}" data-filter="expired" onclick="setFilter('expired')"><?= __('vouchers_filter_expired') ?> (${expiredCnt})</button> <button class="filter-btn ${currentFilter==='expired'?'active':''}" data-filter="expired" onclick="setFilter('expired')"><?= __('vouchers_filter_expired') ?> (${expiredCnt})</button>
</div> </div>
</div> </div>
<div class="table-container"><table class="table"> <div class="table-container"><table class="table table-stack">
<thead><tr> <thead><tr>
<th><?= __('label_created') ?></th> <th><?= __('label_created') ?></th>
<th><?= __('label_code') ?></th> <th><?= __('label_code') ?></th>
@ -330,21 +332,21 @@ function renderVouchers() {
})() : ''; })() : '';
const usagePct = v.quota > 0 ? Math.min(100, (v.used/v.quota)*100) : 0; const usagePct = v.quota > 0 ? Math.min(100, (v.used/v.quota)*100) : 0;
const statusBadge = v.status==='valid' const statusBadge = v.status==='valid'
? `<span class="badge badge-success"><i class="fas fa-check"></i> <?= __('status_valid') ?></span>` ? `<span class="badge badge-success"><i class="fas fa-check" aria-hidden="true"></i> <?= __('status_valid') ?></span>`
: v.status==='used' : v.status==='used'
? `<span class="badge badge-warning"><i class="fas fa-user-check"></i> <?= __('status_used') ?></span>` ? `<span class="badge badge-warning"><i class="fas fa-user-check" aria-hidden="true"></i> <?= __('status_used') ?></span>`
: `<span class="badge badge-danger"><i class="fas fa-times"></i> <?= __('status_expired') ?></span>`; : `<span class="badge badge-danger"><i class="fas fa-times" aria-hidden="true"></i> <?= __('status_expired') ?></span>`;
html += `<tr id="voucher-${v._id}"> html += `<tr id="voucher-${v._id}">
<td><strong>${createDate.toLocaleDateString('de-DE')}</strong><br><small style="color:var(--text-muted)">${createDate.toLocaleTimeString('de-DE',{hour:'2-digit',minute:'2-digit'})}</small></td> <td data-label="<?= __('label_created') ?>"><strong>${createDate.toLocaleDateString(LOCALE)}</strong><br><small style="color:var(--text-muted)">${createDate.toLocaleTimeString(LOCALE,{hour:'2-digit',minute:'2-digit'})}</small></td>
<td><code onclick="copyToClipboard('${escapeHtml(v.formatted_code||'')}','Kopiert!')" title="Kopieren" style="cursor:pointer">${escapeHtml(v.formatted_code||'')}</code></td> <td data-label="<?= __('label_code') ?>"><code onclick="copyToClipboard('${escapeHtml(v.formatted_code||'')}','<?= __('js_copied') ?>')" title="<?= __('js_copy') ?>" style="cursor:pointer">${escapeHtml(v.formatted_code||'')}</code></td>
<td class="voucher-note" title="${escapeHtml(v.note||'-')}">${escapeHtml(v.note||'-')}</td> <td class="voucher-note" data-label="<?= __('label_note') ?>" title="${escapeHtml(v.note||'-')}">${escapeHtml(v.note||'-')}</td>
<td>${statusBadge}</td> <td data-label="<?= __('label_status') ?>">${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 data-label="<?= __('label_usage') ?>"><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 data-label="<?= __('label_expires') ?>">${remaining?`<span style="color:var(--success)"><i class="fas fa-clock" aria-hidden="true"></i> ${remaining}</span><br>`:''}<small style="color:var(--text-muted)">${v.duration} <?= __('label_minutes_short') ?></small></td>
<td style="white-space:nowrap;"> <td data-label="<?= __('label_actions') ?>" 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="resendVoucher('${v._id}','${escapeHtml(v.formatted_code||'')}')" class="btn btn-secondary btn-sm" title="<?= __('vouchers_resend') ?>" aria-label="<?= __('vouchers_resend') ?>"><i class="fas fa-envelope" aria-hidden="true"></i></button>
<button onclick="deleteVoucher('${v._id}')" class="btn btn-danger-soft btn-sm" title="<?= __('btn_delete') ?>"><i class="fas fa-trash"></i></button> <button onclick="deleteVoucher('${v._id}')" class="btn btn-danger-soft btn-sm" title="<?= __('btn_delete') ?>" aria-label="<?= __('btn_delete') ?>"><i class="fas fa-trash" aria-hidden="true"></i></button>
</td> </td>
</tr>`; </tr>`;
}); });
@ -356,11 +358,11 @@ function renderVouchers() {
<span style="font-size:13px;color:var(--text-muted);">${<?= json_encode(__('vouchers_page_of')) ?> <span style="font-size:13px;color:var(--text-muted);">${<?= json_encode(__('vouchers_page_of')) ?>
.replace('{current}',currentPage).replace('{total}',totalPages)} (${vouchers.length})</span> .replace('{current}',currentPage).replace('{total}',totalPages)} (${vouchers.length})</span>
<div style="display:flex;gap:5px;"> <div style="display:flex;gap:5px;">
<button class="btn btn-secondary btn-sm" onclick="setPage(${currentPage-1})" ${currentPage<=1?'disabled':''}><i class="fas fa-chevron-left"></i></button>`; <button class="btn btn-secondary btn-sm" onclick="setPage(${currentPage-1})" ${currentPage<=1?'disabled':''}><i class="fas fa-chevron-left" aria-hidden="true"></i></button>`;
for (let p=Math.max(1,currentPage-2); p<=Math.min(totalPages,currentPage+2); p++) { for (let p=Math.max(1,currentPage-2); p<=Math.min(totalPages,currentPage+2); p++) {
html += `<button class="btn btn-sm ${p===currentPage?'btn-primary':'btn-secondary'}" onclick="setPage(${p})">${p}</button>`; html += `<button class="btn btn-sm ${p===currentPage?'btn-primary':'btn-secondary'}" onclick="setPage(${p})">${p}</button>`;
} }
html += `<button class="btn btn-secondary btn-sm" onclick="setPage(${currentPage+1})" ${currentPage>=totalPages?'disabled':''}><i class="fas fa-chevron-right"></i></button> html += `<button class="btn btn-secondary btn-sm" onclick="setPage(${currentPage+1})" ${currentPage>=totalPages?'disabled':''}><i class="fas fa-chevron-right" aria-hidden="true"></i></button>
</div></div>`; </div></div>`;
} }

View file

@ -27,7 +27,7 @@
/* Text */ /* Text */
--text-primary: #101625; --text-primary: #101625;
--text-secondary: #525c6e; --text-secondary: #525c6e;
--text-muted: #8a93a3; --text-muted: #6b7280;
--text-inverse: #ffffff; --text-inverse: #ffffff;
/* Linien */ /* Linien */
@ -107,7 +107,7 @@
--text-primary: #e9ecf3; --text-primary: #e9ecf3;
--text-secondary: #a2abbd; --text-secondary: #a2abbd;
--text-muted: #6f7889; --text-muted: #8a93a6;
--text-inverse: #0a0c11; --text-inverse: #0a0c11;
--border-color: #232838; --border-color: #232838;
@ -1531,6 +1531,67 @@ input.search-bar, .site-selector .search-bar { min-width: 240px; width: auto; fl
.row3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: 14px; } .row3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: 14px; }
@media (max-width: 720px) { .row, .row3 { grid-template-columns: 1fr; } } @media (max-width: 720px) { .row, .row3 { grid-template-columns: 1fr; } }
/* Sprungmarke zum Inhalt (nur bei Tastaturfokus sichtbar) */
.skip-link {
position: absolute;
left: 12px;
top: -60px;
z-index: 999;
padding: 10px 16px;
background: var(--bg-card);
border: 1px solid var(--accent);
border-radius: var(--r-md);
box-shadow: var(--shadow-md);
color: var(--accent);
font-size: 13.5px;
font-weight: 600;
transition: top .15s;
}
.skip-link:focus { top: 12px; }
/* Tabellen, die auf schmalen Geräten zu Karten werden.
Die Spaltenüberschrift steht dann als data-label vor dem Wert. */
@media (max-width: 720px) {
.table-stack thead { display: none; }
.table-stack, .table-stack tbody, .table-stack tr, .table-stack td { display: block; width: 100%; }
.table-stack tr {
margin-bottom: 10px;
padding: 6px 14px;
border: 1px solid var(--border-color);
border-radius: var(--r-md);
background: var(--bg-card);
}
.table-stack td {
display: flex; align-items: center; justify-content: space-between; gap: 14px;
padding: 8px 0;
border-bottom: 1px solid var(--border-color);
text-align: right;
}
.table-stack tr td:last-child { border-bottom: none; }
.table-stack td::before {
content: attr(data-label);
flex-shrink: 0;
color: var(--text-muted);
font-size: 11.5px;
font-weight: 620;
text-transform: uppercase;
letter-spacing: .04em;
text-align: left;
}
.table-stack td:not([data-label])::before { content: ''; }
.table-stack .details-cell, .table-stack .voucher-note { max-width: none; white-space: normal; }
}
/* Nutzer, die Bewegung reduzieren möchten, bekommen keine Animationen. */
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: .001ms !important;
animation-iteration-count: 1 !important;
transition-duration: .001ms !important;
scroll-behavior: auto !important;
}
}
/* ========================================================================= /* =========================================================================
13. RESPONSIVE 13. RESPONSIVE
========================================================================= */ ========================================================================= */

View file

@ -114,7 +114,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
</form> </form>
<?php endif; ?> <?php endif; ?>
<div class="auth-links"><a href="login.php" class="back-link"><i class="fas fa-arrow-left"></i> <?= __('reset_back_login') ?></a></div> <div class="auth-links"><a href="login.php" class="back-link"><i class="fas fa-arrow-left" aria-hidden="true"></i> <?= __('reset_back_login') ?></a></div>
</div> </div>
<script src="assets/global.js"></script> <script src="assets/global.js"></script>
</body> </body>

View file

@ -48,11 +48,12 @@ foreach ($navGroups as $items) {
</head> </head>
<body> <body>
<a class="skip-link" href="#main-content"><?= __('a11y_skip') ?></a>
<div class="sidebar-overlay" onclick="closeMobileSidebar()"></div> <div class="sidebar-overlay" onclick="closeMobileSidebar()"></div>
<aside class="sidebar" id="adminSidebar"> <aside class="sidebar" id="adminSidebar" aria-label="<?= __('nav_administration') ?>">
<a href="<?= $rootBase ?>index.php" class="sidebar-brand"> <a href="<?= $rootBase ?>index.php" class="sidebar-brand">
<span class="brand-mark"><i class="fas fa-wifi"></i></span> <span class="brand-mark"><i class="fas fa-wifi" aria-hidden="true"></i></span>
<span class="brand-text"> <span class="brand-text">
<span class="brand-name"><?= htmlspecialchars($appTitle ?? 'Voucher Tool') ?></span> <span class="brand-name"><?= htmlspecialchars($appTitle ?? 'Voucher Tool') ?></span>
<span class="brand-sub"><?= __('nav_administration') ?></span> <span class="brand-sub"><?= __('nav_administration') ?></span>
@ -65,8 +66,9 @@ foreach ($navGroups as $items) {
<ul class="sidebar-nav"> <ul class="sidebar-nav">
<?php foreach ($items as [$key, $href, $icon, $labelKey]): ?> <?php foreach ($items as [$key, $href, $icon, $labelKey]): ?>
<li> <li>
<a href="<?= $base . $href ?>" class="<?= $currentPage === $key ? 'active' : '' ?>"> <a href="<?= $base . $href ?>" class="<?= $currentPage === $key ? 'active' : '' ?>"
<i class="fas <?= $icon ?>"></i> <?= __($labelKey) ?> <?= $currentPage === $key ? 'aria-current="page"' : '' ?>>
<i class="fas <?= $icon ?>" aria-hidden="true"></i> <?= __($labelKey) ?>
</a> </a>
</li> </li>
<?php endforeach; ?> <?php endforeach; ?>
@ -82,8 +84,8 @@ foreach ($navGroups as $items) {
<div class="user-name" style="overflow:hidden;text-overflow:ellipsis;white-space:nowrap;"><?= htmlspecialchars($currentUser['name']) ?></div> <div class="user-name" style="overflow:hidden;text-overflow:ellipsis;white-space:nowrap;"><?= htmlspecialchars($currentUser['name']) ?></div>
<div class="user-role"><?= htmlspecialchars($currentUser['role'] ?? '') ?></div> <div class="user-role"><?= htmlspecialchars($currentUser['role'] ?? '') ?></div>
</div> </div>
<a href="<?= $rootBase ?>logout.php" class="icon-btn" title="<?= __('btn_logout') ?>" style="width:30px;height:30px;font-size:12px;"> <a href="<?= $rootBase ?>logout.php" class="icon-btn" title="<?= __('btn_logout') ?>" aria-label="<?= __('btn_logout') ?>" style="width:30px;height:30px;font-size:12px;">
<i class="fas fa-arrow-right-from-bracket"></i> <i class="fas fa-arrow-right-from-bracket" aria-hidden="true"></i>
</a> </a>
</div> </div>
</div> </div>
@ -92,8 +94,8 @@ foreach ($navGroups as $items) {
<header class="topbar"> <header class="topbar">
<div class="header-left"> <div class="header-left">
<button class="mobile-menu-btn" onclick="toggleMobileSidebar()" title="Menu"> <button class="mobile-menu-btn" onclick="toggleMobileSidebar()" aria-label="<?= __('a11y_menu') ?>" title="<?= __('a11y_menu') ?>">
<i class="fas fa-bars"></i> <i class="fas fa-bars" aria-hidden="true"></i>
</button> </button>
<div class="breadcrumb"> <div class="breadcrumb">
<span><?= __('nav_administration') ?></span> <span><?= __('nav_administration') ?></span>
@ -102,20 +104,20 @@ foreach ($navGroups as $items) {
</div> </div>
</div> </div>
<div class="header-right"> <div class="header-right">
<div class="lang-switcher"> <div class="lang-switcher" role="group" aria-label="<?= __('a11y_language') ?>">
<?php foreach (I18n::getAvailable() as $code => $label): ?> <?php foreach (I18n::getAvailable() as $code => $label): ?>
<button class="lang-btn <?= $lang === $code ? 'active' : '' ?>" <button class="lang-btn <?= $lang === $code ? 'active' : '' ?>"
onclick="switchLanguage('<?= $code ?>')"><?= strtoupper($code) ?></button> onclick="switchLanguage('<?= $code ?>')"><?= strtoupper($code) ?></button>
<?php endforeach; ?> <?php endforeach; ?>
</div> </div>
<button id="darkModeBtn" class="dark-mode-toggle" onclick="toggleDarkMode()" title="Dark Mode"> <button id="darkModeBtn" class="dark-mode-toggle" onclick="toggleDarkMode()" aria-label="<?= __('a11y_theme') ?>" title="<?= __('a11y_theme') ?>">
<i class="fas fa-moon"></i> <i class="fas fa-moon" aria-hidden="true"></i>
</button> </button>
<a href="<?= $rootBase ?>index.php" class="btn btn-secondary"> <a href="<?= $rootBase ?>index.php" class="btn btn-secondary">
<i class="fas fa-arrow-left"></i> <i class="fas fa-arrow-left" aria-hidden="true"></i>
<span class="hide-mobile"><?= __('nav_back') ?></span> <span class="hide-mobile"><?= __('nav_back') ?></span>
</a> </a>
</div> </div>
</header> </header>
<main class="main-content"> <main class="main-content" id="main-content">

View file

@ -308,43 +308,44 @@ function buildPrintCard($template, $data, $instructionHeader, $instructionText,
</head> </head>
<body class="app-body"> <body class="app-body">
<a class="skip-link no-print" href="#main-content"><?= __('a11y_skip') ?></a>
<header class="app-topbar no-print"> <header class="app-topbar no-print">
<a href="index.php" class="brand"> <a href="index.php" class="brand">
<span class="brand-mark"><i class="fas fa-wifi"></i></span> <span class="brand-mark"><i class="fas fa-wifi" aria-hidden="true"></i></span>
<span class="brand-name"><?= htmlspecialchars($appTitle) ?></span> <span class="brand-name"><?= htmlspecialchars($appTitle) ?></span>
</a> </a>
<div class="app-actions"> <div class="app-actions">
<div class="lang-switcher"> <div class="lang-switcher" role="group" aria-label="<?= __('a11y_language') ?>">
<?php foreach (I18n::getAvailable() as $code => $label): ?> <?php foreach (I18n::getAvailable() as $code => $label): ?>
<button class="lang-btn <?= I18n::getLanguage() === $code ? 'active' : '' ?>" <button class="lang-btn <?= I18n::getLanguage() === $code ? 'active' : '' ?>"
onclick="switchLanguage('<?= $code ?>')"><?= strtoupper($code) ?></button> onclick="switchLanguage('<?= $code ?>')"><?= strtoupper($code) ?></button>
<?php endforeach; ?> <?php endforeach; ?>
</div> </div>
<button id="darkModeBtn" class="dark-mode-toggle" onclick="toggleDarkMode()" title="Dark Mode"> <button id="darkModeBtn" class="dark-mode-toggle" onclick="toggleDarkMode()" aria-label="<?= __('a11y_theme') ?>" title="<?= __('a11y_theme') ?>">
<i class="fas fa-moon"></i> <i class="fas fa-moon" aria-hidden="true"></i>
</button> </button>
<?php if ($currentUser): ?> <?php if ($currentUser): ?>
<?php if ($auth->isAdmin()): ?> <?php if ($auth->isAdmin()): ?>
<a href="admin/" class="btn btn-secondary"> <a href="admin/" class="btn btn-secondary">
<i class="fas fa-sliders"></i> <span class="hide-mobile"><?= __('nav_administration') ?></span> <i class="fas fa-sliders" aria-hidden="true"></i> <span class="hide-mobile"><?= __('nav_administration') ?></span>
</a> </a>
<?php endif; ?> <?php endif; ?>
<div class="user-menu"> <div class="user-menu">
<div class="user-avatar"><?= strtoupper(mb_substr($currentUser['name'], 0, 1)) ?></div> <div class="user-avatar"><?= strtoupper(mb_substr($currentUser['name'], 0, 1)) ?></div>
<div class="user-name hide-mobile"><?= htmlspecialchars($currentUser['name']) ?></div> <div class="user-name hide-mobile"><?= htmlspecialchars($currentUser['name']) ?></div>
<a href="logout.php" class="icon-btn" title="<?= __('btn_logout') ?>" style="width:28px;height:28px;font-size:11px;"> <a href="logout.php" class="icon-btn" title="<?= __('btn_logout') ?>" aria-label="<?= __('btn_logout') ?>" style="width:28px;height:28px;font-size:11px;">
<i class="fas fa-arrow-right-from-bracket"></i> <i class="fas fa-arrow-right-from-bracket" aria-hidden="true"></i>
</a> </a>
</div> </div>
<?php elseif ($publicAccess): ?> <?php elseif ($publicAccess): ?>
<a href="login.php" class="btn btn-secondary"> <a href="login.php" class="btn btn-secondary">
<i class="fas fa-right-to-bracket"></i> <?= __('btn_login') ?> <i class="fas fa-right-to-bracket" aria-hidden="true"></i> <?= __('btn_login') ?>
</a> </a>
<?php endif; ?> <?php endif; ?>
</div> </div>
</header> </header>
<div class="container"> <main class="container" id="main-content">
<?php if ($logoUrl && !$voucherCreated && !$bulkCreated): ?> <?php if ($logoUrl && !$voucherCreated && !$bulkCreated): ?>
<img src="<?= htmlspecialchars(Ui::mediaUrl($logoUrl)) ?>" alt="Logo" class="logo"> <img src="<?= htmlspecialchars(Ui::mediaUrl($logoUrl)) ?>" alt="Logo" class="logo">
<?php endif; ?> <?php endif; ?>
@ -366,17 +367,17 @@ function buildPrintCard($template, $data, $instructionHeader, $instructionText,
</div> </div>
<div class="voucher-result no-print"> <div class="voucher-result no-print">
<div class="result-label"><i class="fas fa-circle-check"></i> <?= __('voucher_success_title') ?></div> <div class="result-label"><i class="fas fa-circle-check" aria-hidden="true"></i> <?= __('voucher_success_title') ?></div>
<div class="voucher-code" id="voucherCode" onclick="copyCode()" title="<?= __('js_click_to_copy') ?>"> <div class="voucher-code" id="voucherCode" onclick="copyCode()" title="<?= __('js_click_to_copy') ?>">
<?= htmlspecialchars($voucherCode) ?> <?= htmlspecialchars($voucherCode) ?>
</div> </div>
<div class="voucher-info"> <div class="voucher-info">
<i class="fas fa-copy"></i> <?= __('voucher_copy_hint') ?> <i class="fas fa-copy" aria-hidden="true"></i> <?= __('voucher_copy_hint') ?>
</div> </div>
<div class="voucher-meta"> <div class="voucher-meta">
<span><i class="fas fa-clock"></i> <?= str_replace('{minutes}', $voucherData['expire_min'], __('voucher_validity')) ?></span> <span><i class="fas fa-clock" aria-hidden="true"></i> <?= str_replace('{minutes}', $voucherData['expire_min'], __('voucher_validity')) ?></span>
<span><i class="fas fa-location-dot"></i> <?= htmlspecialchars($voucherData['site_name']) ?></span> <span><i class="fas fa-location-dot" aria-hidden="true"></i> <?= htmlspecialchars($voucherData['site_name']) ?></span>
<span><i class="fas fa-mobile-screen"></i> <?= (int)$voucherData['max_uses'] ?> <?= __('label_devices') ?></span> <span><i class="fas fa-mobile-screen" aria-hidden="true"></i> <?= (int)$voucherData['max_uses'] ?> <?= __('label_devices') ?></span>
</div> </div>
<div class="ticket-divider"></div> <div class="ticket-divider"></div>
<div class="qr-wrapper no-print"> <div class="qr-wrapper no-print">
@ -395,11 +396,11 @@ function buildPrintCard($template, $data, $instructionHeader, $instructionText,
<div class="no-print" style="display:flex;gap:10px;margin-top:18px;"> <div class="no-print" style="display:flex;gap:10px;margin-top:18px;">
<form method="get" style="flex:1;"> <form method="get" style="flex:1;">
<button type="submit" class="btn btn-primary btn-lg btn-block"> <button type="submit" class="btn btn-primary btn-lg btn-block">
<i class="fas fa-plus"></i> <?= __('btn_new_code') ?> <i class="fas fa-plus" aria-hidden="true"></i> <?= __('btn_new_code') ?>
</button> </button>
</form> </form>
<button onclick="window.print()" class="btn btn-secondary btn-lg"> <button onclick="window.print()" class="btn btn-secondary btn-lg">
<i class="fas fa-print"></i> <?= __('voucher_print_btn') ?> <i class="fas fa-print" aria-hidden="true"></i> <?= __('voucher_print_btn') ?>
</button> </button>
</div> </div>
@ -441,12 +442,12 @@ function buildPrintCard($template, $data, $instructionHeader, $instructionText,
<?php endforeach; ?> <?php endforeach; ?>
</tbody> </tbody>
</table> </table>
<p class="copy-hint"><i class="fas fa-copy"></i> <?= __('voucher_copy_hint') ?></p> <p class="copy-hint"><i class="fas fa-copy" aria-hidden="true"></i> <?= __('voucher_copy_hint') ?></p>
</div> </div>
<div class="no-print" style="display:flex;gap:10px;margin-top:20px;"> <div class="no-print" style="display:flex;gap:10px;margin-top:20px;">
<button onclick="window.print()" class="btn btn-primary btn-lg" style="flex:1;"> <button onclick="window.print()" class="btn btn-primary btn-lg" style="flex:1;">
<i class="fas fa-print"></i> <?= __('bulk_print_all') ?> <i class="fas fa-print" aria-hidden="true"></i> <?= __('bulk_print_all') ?>
</button> </button>
<form method="get" style="flex:1;"> <form method="get" style="flex:1;">
<button type="submit" class="btn btn-secondary btn-lg btn-block"><?= __('btn_new_code') ?></button> <button type="submit" class="btn btn-secondary btn-lg btn-block"><?= __('btn_new_code') ?></button>
@ -455,7 +456,7 @@ function buildPrintCard($template, $data, $instructionHeader, $instructionText,
<?php elseif (empty($sites)): ?> <?php elseif (empty($sites)): ?>
<div class="empty-state"> <div class="empty-state">
<div class="empty-icon"><i class="fas fa-wifi"></i></div> <div class="empty-icon"><i class="fas fa-wifi" aria-hidden="true"></i></div>
<p><?= __('voucher_no_sites') ?><br> <p><?= __('voucher_no_sites') ?><br>
<?php if ($auth->isAdmin()): ?> <?php if ($auth->isAdmin()): ?>
<a href="admin/" style="color:var(--accent);"><?= __('voucher_no_sites_admin') ?></a> <a href="admin/" style="color:var(--accent);"><?= __('voucher_no_sites_admin') ?></a>
@ -539,7 +540,7 @@ function buildPrintCard($template, $data, $instructionHeader, $instructionText,
<div class="email-checkbox-wrapper"> <div class="email-checkbox-wrapper">
<input type="checkbox" id="send_email" name="send_email" onchange="toggleEmailField()"> <input type="checkbox" id="send_email" name="send_email" onchange="toggleEmailField()">
<label for="send_email"> <label for="send_email">
<i class="fas fa-envelope" style="color:var(--accent);"></i> <?= __('voucher_email_send') ?> <i class="fas fa-envelope" style="color:var(--accent);" aria-hidden="true"></i> <?= __('voucher_email_send') ?>
</label> </label>
</div> </div>
<div class="email-input-wrapper" id="email_field"> <div class="email-input-wrapper" id="email_field">
@ -556,7 +557,7 @@ function buildPrintCard($template, $data, $instructionHeader, $instructionText,
<div class="email-option"> <div class="email-option">
<div class="email-checkbox-wrapper"> <div class="email-checkbox-wrapper">
<input type="checkbox" id="send_sms" name="send_sms" onchange="document.getElementById('sms_field').style.display=this.checked?'block':'none'"> <input type="checkbox" id="send_sms" name="send_sms" onchange="document.getElementById('sms_field').style.display=this.checked?'block':'none'">
<label for="send_sms"><i class="fas fa-comment-sms" style="color:var(--accent);"></i> Code per SMS versenden</label> <label for="send_sms"><i class="fas fa-comment-sms" style="color:var(--accent);" aria-hidden="true"></i> Code per SMS versenden</label>
</div> </div>
<div id="sms_field" style="display:none;margin-top:12px;"> <div id="sms_field" style="display:none;margin-top:12px;">
<label for="recipient_phone">Telefonnummer (international, z.B. +49170)</label> <label for="recipient_phone">Telefonnummer (international, z.B. +49170)</label>
@ -566,7 +567,7 @@ function buildPrintCard($template, $data, $instructionHeader, $instructionText,
<?php endif; ?> <?php endif; ?>
<button type="submit" class="btn btn-primary btn-lg btn-block" id="submitBtn"> <button type="submit" class="btn btn-primary btn-lg btn-block" id="submitBtn">
<i class="fas fa-ticket"></i> <?= __('voucher_create_btn') ?> <i class="fas fa-ticket" aria-hidden="true"></i> <?= __('voucher_create_btn') ?>
</button> </button>
</form> </form>
</div> </div>
@ -629,9 +630,9 @@ function buildPrintCard($template, $data, $instructionHeader, $instructionText,
<?php endif; ?> <?php endif; ?>
<?php endif; ?> <?php endif; ?>
</div> </main>
<div id="toast-container"></div> <div id="toast-container" role="status" aria-live="polite"></div>
<script src="assets/global.js"></script> <script src="assets/global.js"></script>
<script> <script>
<?php if ($voucherCreated): ?> <?php if ($voucherCreated): ?>
@ -711,7 +712,7 @@ function buildPrintCard($template, $data, $instructionHeader, $instructionText,
const btn = document.getElementById('submitBtn'); const btn = document.getElementById('submitBtn');
if (!btn || btn.disabled) { e.preventDefault(); return; } if (!btn || btn.disabled) { e.preventDefault(); return; }
btn.disabled = true; btn.disabled = true;
btn.innerHTML = '<i class="fas fa-circle-notch fa-spin"></i> <?= __('voucher_creating') ?>'; btn.innerHTML = '<i class="fas fa-circle-notch fa-spin" aria-hidden="true"></i> <?= __('voucher_creating') ?>';
}); });
document.getElementById('bulkForm')?.addEventListener('submit', function(e) { document.getElementById('bulkForm')?.addEventListener('submit', function(e) {
@ -719,7 +720,7 @@ function buildPrintCard($template, $data, $instructionHeader, $instructionText,
if (!btn || btn.disabled) { e.preventDefault(); return; } if (!btn || btn.disabled) { e.preventDefault(); return; }
const count = document.getElementById('bulk_count').value; const count = document.getElementById('bulk_count').value;
btn.disabled = true; btn.disabled = true;
btn.innerHTML = '<i class="fas fa-circle-notch fa-spin"></i> <?= addslashes(str_replace('{count}', "' + count + '", __('bulk_creating'))) ?>'; btn.innerHTML = '<i class="fas fa-circle-notch fa-spin" aria-hidden="true"></i> <?= addslashes(str_replace('{count}', "' + count + '", __('bulk_creating'))) ?>';
}); });
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function() {

View file

@ -1,380 +1,380 @@
<?php <?php
session_start(); session_start();
// Prüfen ob bereits installiert. // Prüfen ob bereits installiert.
// Wenn eine config.php existiert, darf der Installer NICHT mehr ohne Weiteres // Wenn eine config.php existiert, darf der Installer NICHT mehr ohne Weiteres
// erreichbar sein sonst koennte jeder die Konfiguration ueberschreiben und // erreichbar sein sonst koennte jeder die Konfiguration ueberschreiben und
// einen neuen Admin anlegen. Reinstall ist nur fuer angemeldete Admins erlaubt. // einen neuen Admin anlegen. Reinstall ist nur fuer angemeldete Admins erlaubt.
if (file_exists(__DIR__ . '/config.php')) { if (file_exists(__DIR__ . '/config.php')) {
if (!isset($_GET['reinstall'])) { if (!isset($_GET['reinstall'])) {
die('System bereits installiert. Eine Neuinstallation ist nur fuer angemeldete Administratoren ueber install.php?reinstall=1 moeglich.'); die('System bereits installiert. Eine Neuinstallation ist nur fuer angemeldete Administratoren ueber install.php?reinstall=1 moeglich.');
} }
// Reinstall angefordert -> Admin-Authentifizierung erzwingen // Reinstall angefordert -> Admin-Authentifizierung erzwingen
require_once __DIR__ . '/config.php'; require_once __DIR__ . '/config.php';
require_once __DIR__ . '/includes/Database.php'; require_once __DIR__ . '/includes/Database.php';
require_once __DIR__ . '/includes/Auth.php'; require_once __DIR__ . '/includes/Auth.php';
try { try {
$reinstallAuth = new Auth(); $reinstallAuth = new Auth();
if (!$reinstallAuth->isAdmin()) { if (!$reinstallAuth->isAdmin()) {
die('Neuinstallation nicht erlaubt: Bitte zuerst als Administrator <a href="login.php">anmelden</a>.'); die('Neuinstallation nicht erlaubt: Bitte zuerst als Administrator <a href="login.php">anmelden</a>.');
} }
} catch (Exception $e) { } catch (Exception $e) {
die('Neuinstallation nicht moeglich (Konfigurationsfehler).'); die('Neuinstallation nicht moeglich (Konfigurationsfehler).');
} }
} }
$step = isset($_POST['step']) ? (int)$_POST['step'] : 1; $step = isset($_POST['step']) ? (int)$_POST['step'] : 1;
$errors = []; $errors = [];
$success = false; $success = false;
// Step 1: Datenbankverbindung testen // Step 1: Datenbankverbindung testen
if ($step === 2 && $_SERVER['REQUEST_METHOD'] === 'POST') { if ($step === 2 && $_SERVER['REQUEST_METHOD'] === 'POST') {
$db_host = $_POST['db_host'] ?? ''; $db_host = $_POST['db_host'] ?? '';
$db_name = $_POST['db_name'] ?? ''; $db_name = $_POST['db_name'] ?? '';
$db_user = $_POST['db_user'] ?? ''; $db_user = $_POST['db_user'] ?? '';
$db_pass = $_POST['db_pass'] ?? ''; $db_pass = $_POST['db_pass'] ?? '';
try { try {
$pdo = new PDO("mysql:host=$db_host;charset=utf8mb4", $db_user, $db_pass); $pdo = new PDO("mysql:host=$db_host;charset=utf8mb4", $db_user, $db_pass);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// Datenbank erstellen falls nicht vorhanden // Datenbank erstellen falls nicht vorhanden
$pdo->exec("CREATE DATABASE IF NOT EXISTS `$db_name` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci"); $pdo->exec("CREATE DATABASE IF NOT EXISTS `$db_name` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci");
$pdo->exec("USE `$db_name`"); $pdo->exec("USE `$db_name`");
// Tabellen erstellen // Tabellen erstellen
$sql = file_get_contents(__DIR__ . '/database.sql'); $sql = file_get_contents(__DIR__ . '/database.sql');
$pdo->exec($sql); $pdo->exec($sql);
$_SESSION['install_db'] = [ $_SESSION['install_db'] = [
'host' => $db_host, 'host' => $db_host,
'name' => $db_name, 'name' => $db_name,
'user' => $db_user, 'user' => $db_user,
'pass' => $db_pass 'pass' => $db_pass
]; ];
} catch (PDOException $e) { } catch (PDOException $e) {
$errors[] = "Datenbankfehler: " . $e->getMessage(); $errors[] = "Datenbankfehler: " . $e->getMessage();
$step = 1; $step = 1;
} }
} }
// Step 2: Admin-Account erstellen // Step 2: Admin-Account erstellen
if ($step === 3 && $_SERVER['REQUEST_METHOD'] === 'POST') { if ($step === 3 && $_SERVER['REQUEST_METHOD'] === 'POST') {
$admin_email = filter_var($_POST['admin_email'] ?? '', FILTER_VALIDATE_EMAIL); $admin_email = filter_var($_POST['admin_email'] ?? '', FILTER_VALIDATE_EMAIL);
$admin_name = $_POST['admin_name'] ?? ''; $admin_name = $_POST['admin_name'] ?? '';
$admin_password = $_POST['admin_password'] ?? ''; $admin_password = $_POST['admin_password'] ?? '';
$admin_password_confirm = $_POST['admin_password_confirm'] ?? ''; $admin_password_confirm = $_POST['admin_password_confirm'] ?? '';
if (!$admin_email) { if (!$admin_email) {
$errors[] = "Ungültige E-Mail-Adresse"; $errors[] = "Ungültige E-Mail-Adresse";
$step = 2; $step = 2;
} elseif (strlen($admin_password) < 8) { } elseif (strlen($admin_password) < 8) {
$errors[] = "Passwort muss mindestens 8 Zeichen lang sein"; $errors[] = "Passwort muss mindestens 8 Zeichen lang sein";
$step = 2; $step = 2;
} elseif ($admin_password !== $admin_password_confirm) { } elseif ($admin_password !== $admin_password_confirm) {
$errors[] = "Passwörter stimmen nicht überein"; $errors[] = "Passwörter stimmen nicht überein";
$step = 2; $step = 2;
} else { } else {
$_SESSION['install_admin'] = [ $_SESSION['install_admin'] = [
'email' => $admin_email, 'email' => $admin_email,
'name' => $admin_name, 'name' => $admin_name,
'password' => password_hash($admin_password, PASSWORD_DEFAULT) 'password' => password_hash($admin_password, PASSWORD_DEFAULT)
]; ];
} }
} }
// Step 3: Allgemeine Einstellungen // Step 3: Allgemeine Einstellungen
if ($step === 4 && $_SERVER['REQUEST_METHOD'] === 'POST') { if ($step === 4 && $_SERVER['REQUEST_METHOD'] === 'POST') {
$app_title = $_POST['app_title'] ?? 'UniFi Voucher System'; $app_title = $_POST['app_title'] ?? 'UniFi Voucher System';
$logo_url = $_POST['logo_url'] ?? ''; $logo_url = $_POST['logo_url'] ?? '';
$instruction_header = $_POST['instruction_header'] ?? ''; $instruction_header = $_POST['instruction_header'] ?? '';
$instruction_text = $_POST['instruction_text'] ?? ''; $instruction_text = $_POST['instruction_text'] ?? '';
$public_access = isset($_POST['public_access']) ? 1 : 0; $public_access = isset($_POST['public_access']) ? 1 : 0;
// Microsoft 365 OAuth (optional) // Microsoft 365 OAuth (optional)
$m365_client_id = $_POST['m365_client_id'] ?? ''; $m365_client_id = $_POST['m365_client_id'] ?? '';
$m365_client_secret = $_POST['m365_client_secret'] ?? ''; $m365_client_secret = $_POST['m365_client_secret'] ?? '';
$m365_tenant_id = $_POST['m365_tenant_id'] ?? ''; $m365_tenant_id = $_POST['m365_tenant_id'] ?? '';
$_SESSION['install_settings'] = [ $_SESSION['install_settings'] = [
'app_title' => $app_title, 'app_title' => $app_title,
'logo_url' => $logo_url, 'logo_url' => $logo_url,
'instruction_header' => $instruction_header, 'instruction_header' => $instruction_header,
'instruction_text' => $instruction_text, 'instruction_text' => $instruction_text,
'public_access' => $public_access, 'public_access' => $public_access,
'm365_client_id' => $m365_client_id, 'm365_client_id' => $m365_client_id,
'm365_client_secret' => $m365_client_secret, 'm365_client_secret' => $m365_client_secret,
'm365_tenant_id' => $m365_tenant_id 'm365_tenant_id' => $m365_tenant_id
]; ];
} }
// Step 4: Installation abschließen // Step 4: Installation abschließen
if ($step === 5 && $_SERVER['REQUEST_METHOD'] === 'POST') { if ($step === 5 && $_SERVER['REQUEST_METHOD'] === 'POST') {
try { try {
$db = $_SESSION['install_db']; $db = $_SESSION['install_db'];
$admin = $_SESSION['install_admin']; $admin = $_SESSION['install_admin'];
$settings = $_SESSION['install_settings']; $settings = $_SESSION['install_settings'];
$pdo = new PDO("mysql:host={$db['host']};dbname={$db['name']};charset=utf8mb4", $db['user'], $db['pass']); $pdo = new PDO("mysql:host={$db['host']};dbname={$db['name']};charset=utf8mb4", $db['user'], $db['pass']);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// Admin-User erstellen // Admin-User erstellen
$stmt = $pdo->prepare("INSERT INTO users (email, name, password_hash, is_admin, is_active) VALUES (?, ?, ?, 1, 1)"); $stmt = $pdo->prepare("INSERT INTO users (email, name, password_hash, is_admin, is_active) VALUES (?, ?, ?, 1, 1)");
$stmt->execute([$admin['email'], $admin['name'], $admin['password']]); $stmt->execute([$admin['email'], $admin['name'], $admin['password']]);
// Settings speichern // Settings speichern
$settingsData = [ $settingsData = [
'app_title' => $settings['app_title'], 'app_title' => $settings['app_title'],
'logo_url' => $settings['logo_url'], 'logo_url' => $settings['logo_url'],
'instruction_header' => $settings['instruction_header'], 'instruction_header' => $settings['instruction_header'],
'instruction_text' => $settings['instruction_text'], 'instruction_text' => $settings['instruction_text'],
'public_access' => $settings['public_access'], 'public_access' => $settings['public_access'],
'm365_client_id' => $settings['m365_client_id'], 'm365_client_id' => $settings['m365_client_id'],
'm365_client_secret' => $settings['m365_client_secret'], 'm365_client_secret' => $settings['m365_client_secret'],
'm365_tenant_id' => $settings['m365_tenant_id'] 'm365_tenant_id' => $settings['m365_tenant_id']
]; ];
$stmt = $pdo->prepare("INSERT INTO settings (setting_key, setting_value) VALUES (?, ?) ON DUPLICATE KEY UPDATE setting_value = VALUES(setting_value)"); $stmt = $pdo->prepare("INSERT INTO settings (setting_key, setting_value) VALUES (?, ?) ON DUPLICATE KEY UPDATE setting_value = VALUES(setting_value)");
foreach ($settingsData as $key => $value) { foreach ($settingsData as $key => $value) {
$stmt->execute([$key, $value]); $stmt->execute([$key, $value]);
} }
// config.php erstellen // config.php erstellen
$configContent = "<?php\n"; $configContent = "<?php\n";
$configContent .= "// UniFi Voucher Management System - Configuration\n\n"; $configContent .= "// UniFi Voucher Management System - Configuration\n\n";
$configContent .= "define('DB_HOST', '{$db['host']}');\n"; $configContent .= "define('DB_HOST', '{$db['host']}');\n";
$configContent .= "define('DB_NAME', '{$db['name']}');\n"; $configContent .= "define('DB_NAME', '{$db['name']}');\n";
$configContent .= "define('DB_USER', '{$db['user']}');\n"; $configContent .= "define('DB_USER', '{$db['user']}');\n";
$configContent .= "define('DB_PASS', '" . addslashes($db['pass']) . "');\n\n"; $configContent .= "define('DB_PASS', '" . addslashes($db['pass']) . "');\n\n";
$configContent .= "// Anwendungs-Schluessel fuer Verschluesselung-at-rest (z.B. UniFi-Passwoerter)\n"; $configContent .= "// Anwendungs-Schluessel fuer Verschluesselung-at-rest (z.B. UniFi-Passwoerter)\n";
$configContent .= "// NICHT aendern, sonst koennen bestehende verschluesselte Werte nicht mehr gelesen werden.\n"; $configContent .= "// NICHT aendern, sonst koennen bestehende verschluesselte Werte nicht mehr gelesen werden.\n";
$configContent .= "define('APP_KEY', '" . base64_encode(random_bytes(32)) . "');\n\n"; $configContent .= "define('APP_KEY', '" . base64_encode(random_bytes(32)) . "');\n\n";
$configContent .= "// Sitzungs-Einstellungen\n"; $configContent .= "// Sitzungs-Einstellungen\n";
$configContent .= "define('SESSION_LIFETIME', 3600); // 1 Stunde\n\n"; $configContent .= "define('SESSION_LIFETIME', 3600); // 1 Stunde\n\n";
$configContent .= "// Zeitzone\n"; $configContent .= "// Zeitzone\n";
$configContent .= "date_default_timezone_set('Europe/Berlin');\n"; $configContent .= "date_default_timezone_set('Europe/Berlin');\n";
file_put_contents(__DIR__ . '/config.php', $configContent); file_put_contents(__DIR__ . '/config.php', $configContent);
// .htaccess erstellen (ohne Rewrite Rules die Probleme machen) // .htaccess erstellen (ohne Rewrite Rules die Probleme machen)
$htaccess = "# UniFi Voucher System\n\n"; $htaccess = "# UniFi Voucher System\n\n";
$htaccess .= "# Security\n"; $htaccess .= "# Security\n";
$htaccess .= "<FilesMatch \"(config\\.php|database\\.sql|install\\.php|test\\.php|m365_debug\\.php|\\.md)$\">\n"; $htaccess .= "<FilesMatch \"(config\\.php|database\\.sql|install\\.php|test\\.php|m365_debug\\.php|\\.md)$\">\n";
$htaccess .= " Order Allow,Deny\n"; $htaccess .= " Order Allow,Deny\n";
$htaccess .= " Deny from all\n"; $htaccess .= " Deny from all\n";
$htaccess .= "</FilesMatch>\n\n"; $htaccess .= "</FilesMatch>\n\n";
$htaccess .= "DirectoryIndex index.php\n"; $htaccess .= "DirectoryIndex index.php\n";
file_put_contents(__DIR__ . '/.htaccess', $htaccess); file_put_contents(__DIR__ . '/.htaccess', $htaccess);
$success = true; $success = true;
// Session-Daten löschen // Session-Daten löschen
unset($_SESSION['install_db'], $_SESSION['install_admin'], $_SESSION['install_settings']); unset($_SESSION['install_db'], $_SESSION['install_admin'], $_SESSION['install_settings']);
} catch (Exception $e) { } catch (Exception $e) {
$errors[] = "Fehler bei der Installation: " . $e->getMessage(); $errors[] = "Fehler bei der Installation: " . $e->getMessage();
$step = 4; $step = 4;
} }
} }
?> ?>
<!DOCTYPE html> <!DOCTYPE html>
<html lang="de"> <html lang="de">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>UniFi Voucher System - Installation</title> <title>UniFi Voucher System - Installation</title>
<?php require_once __DIR__ . '/includes/Ui.php'; ?> <?php require_once __DIR__ . '/includes/Ui.php'; ?>
<?= Ui::head(null) ?> <?= Ui::head(null) ?>
<style> <style>
/* Installer-spezifisch: Fortschrittsanzeige und Abschnitte */ /* Installer-spezifisch: Fortschrittsanzeige und Abschnitte */
.install-head { display: flex; align-items: center; gap: 14px; margin-bottom: 4px; } .install-head { display: flex; align-items: center; gap: 14px; margin-bottom: 4px; }
.progress { position: relative; display: flex; justify-content: space-between; margin: 26px 0 28px; } .progress { position: relative; display: flex; justify-content: space-between; margin: 26px 0 28px; }
.progress::before { .progress::before {
content: ''; position: absolute; top: 14px; left: 14px; right: 14px; content: ''; position: absolute; top: 14px; left: 14px; right: 14px;
height: 2px; background: var(--border-color); z-index: 0; height: 2px; background: var(--border-color); z-index: 0;
} }
.progress-step { .progress-step {
position: relative; z-index: 1; position: relative; z-index: 1;
width: 28px; height: 28px; border-radius: 50%; width: 28px; height: 28px; border-radius: 50%;
display: flex; align-items: center; justify-content: center; display: flex; align-items: center; justify-content: center;
background: var(--bg-card); border: 1px solid var(--border-color); background: var(--bg-card); border: 1px solid var(--border-color);
color: var(--text-muted); font-size: 12px; font-weight: 650; color: var(--text-muted); font-size: 12px; font-weight: 650;
} }
.progress-step.active { background: var(--accent); border-color: var(--accent); color: #fff; box-shadow: var(--ring); } .progress-step.active { background: var(--accent); border-color: var(--accent); color: #fff; box-shadow: var(--ring); }
.progress-step.completed { background: var(--success); border-color: var(--success); color: #fff; } .progress-step.completed { background: var(--success); border-color: var(--success); color: #fff; }
.section { .section {
padding: 18px; padding: 18px;
margin-bottom: 20px; margin-bottom: 20px;
background: var(--bg-subtle); background: var(--bg-subtle);
border: 1px solid var(--border-color); border: 1px solid var(--border-color);
border-radius: var(--r-md); border-radius: var(--r-md);
} }
.section h3 { font-size: 14px; margin-bottom: 14px; } .section h3 { font-size: 14px; margin-bottom: 14px; }
.error, .success { .error, .success {
padding: 12px 15px; margin-bottom: 20px; padding: 12px 15px; margin-bottom: 20px;
border-radius: var(--r-md); font-size: 13.5px; border-radius: var(--r-md); font-size: 13.5px;
border: 1px solid var(--danger-border); background: var(--danger-soft); color: var(--danger); border: 1px solid var(--danger-border); background: var(--danger-soft); color: var(--danger);
} }
.success { border-color: var(--success-border); background: var(--success-soft); color: var(--success); } .success { border-color: var(--success-border); background: var(--success-soft); color: var(--success); }
form > h3 { font-size: 15px; margin-bottom: 16px; } form > h3 { font-size: 15px; margin-bottom: 16px; }
</style> </style>
</head> </head>
<body class="app-body focus-page"> <body class="app-body focus-page">
<div class="focus-card card"> <div class="focus-card card">
<div class="install-head"> <div class="install-head">
<span class="focus-icon"><i class="fas fa-rocket"></i></span> <span class="focus-icon"><i class="fas fa-rocket" aria-hidden="true"></i></span>
<div> <div>
<h1 style="font-size:20px;">UniFi Voucher System</h1> <h1 style="font-size:20px;">UniFi Voucher System</h1>
<p class="sub">Installation in fünf Schritten</p> <p class="sub">Installation in fünf Schritten</p>
</div> </div>
</div> </div>
<div class="progress"> <div class="progress">
<div class="progress-step <?= $step >= 1 ? 'completed' : '' ?>">1</div> <div class="progress-step <?= $step >= 1 ? 'completed' : '' ?>">1</div>
<div class="progress-step <?= $step >= 2 ? 'completed' : ($step === 1 ? 'active' : '') ?>">2</div> <div class="progress-step <?= $step >= 2 ? 'completed' : ($step === 1 ? 'active' : '') ?>">2</div>
<div class="progress-step <?= $step >= 3 ? 'completed' : ($step === 2 ? 'active' : '') ?>">3</div> <div class="progress-step <?= $step >= 3 ? 'completed' : ($step === 2 ? 'active' : '') ?>">3</div>
<div class="progress-step <?= $step >= 4 ? 'completed' : ($step === 3 ? 'active' : '') ?>">4</div> <div class="progress-step <?= $step >= 4 ? 'completed' : ($step === 3 ? 'active' : '') ?>">4</div>
<div class="progress-step <?= $step >= 5 ? 'active' : '' ?>">5</div> <div class="progress-step <?= $step >= 5 ? 'active' : '' ?>">5</div>
</div> </div>
<?php if (!empty($errors)): ?> <?php if (!empty($errors)): ?>
<div class="error"> <div class="error">
<?php foreach ($errors as $error): ?> <?php foreach ($errors as $error): ?>
<div><?= htmlspecialchars($error) ?></div> <div><?= htmlspecialchars($error) ?></div>
<?php endforeach; ?> <?php endforeach; ?>
</div> </div>
<?php endif; ?> <?php endif; ?>
<?php if ($success): ?> <?php if ($success): ?>
<div class="success"> <div class="success">
<strong> Installation erfolgreich abgeschlossen!</strong><br> <strong> Installation erfolgreich abgeschlossen!</strong><br>
Sie können sich jetzt mit Ihren Admin-Zugangsdaten anmelden. Sie können sich jetzt mit Ihren Admin-Zugangsdaten anmelden.
</div> </div>
<a href="index.php" class="btn btn-primary btn-lg btn-block">Zum Login</a> <a href="index.php" class="btn btn-primary btn-lg btn-block">Zum Login</a>
<?php elseif ($step === 1): ?> <?php elseif ($step === 1): ?>
<form method="post"> <form method="post">
<input type="hidden" name="step" value="2"> <input type="hidden" name="step" value="2">
<h3>Schritt 1: Datenbank-Konfiguration</h3> <h3>Schritt 1: Datenbank-Konfiguration</h3>
<div class="form-group"> <div class="form-group">
<label>Datenbank-Host</label> <label>Datenbank-Host</label>
<input type="text" name="db_host" value="localhost" required> <input type="text" name="db_host" value="localhost" required>
<div class="help-text">Meist "localhost"</div> <div class="help-text">Meist "localhost"</div>
</div> </div>
<div class="form-group"> <div class="form-group">
<label>Datenbankname</label> <label>Datenbankname</label>
<input type="text" name="db_name" value="unifi_voucher" required> <input type="text" name="db_name" value="unifi_voucher" required>
<div class="help-text">Name der Datenbank (wird erstellt falls nicht vorhanden)</div> <div class="help-text">Name der Datenbank (wird erstellt falls nicht vorhanden)</div>
</div> </div>
<div class="form-group"> <div class="form-group">
<label>Datenbank-Benutzer</label> <label>Datenbank-Benutzer</label>
<input type="text" name="db_user" required> <input type="text" name="db_user" required>
</div> </div>
<div class="form-group"> <div class="form-group">
<label>Datenbank-Passwort</label> <label>Datenbank-Passwort</label>
<input type="password" name="db_pass"> <input type="password" name="db_pass">
</div> </div>
<button type="submit" class="btn btn-primary btn-lg btn-block">Weiter <i class="fas fa-arrow-right"></i></button> <button type="submit" class="btn btn-primary btn-lg btn-block">Weiter <i class="fas fa-arrow-right" aria-hidden="true"></i></button>
</form> </form>
<?php elseif ($step === 2): ?> <?php elseif ($step === 2): ?>
<form method="post"> <form method="post">
<input type="hidden" name="step" value="3"> <input type="hidden" name="step" value="3">
<h3>Schritt 2: Administrator-Account</h3> <h3>Schritt 2: Administrator-Account</h3>
<div class="form-group"> <div class="form-group">
<label>Name</label> <label>Name</label>
<input type="text" name="admin_name" required> <input type="text" name="admin_name" required>
</div> </div>
<div class="form-group"> <div class="form-group">
<label>E-Mail</label> <label>E-Mail</label>
<input type="email" name="admin_email" required> <input type="email" name="admin_email" required>
</div> </div>
<div class="form-group"> <div class="form-group">
<label>Passwort</label> <label>Passwort</label>
<input type="password" name="admin_password" required minlength="8"> <input type="password" name="admin_password" required minlength="8">
<div class="help-text">Mindestens 8 Zeichen</div> <div class="help-text">Mindestens 8 Zeichen</div>
</div> </div>
<div class="form-group"> <div class="form-group">
<label>Passwort bestätigen</label> <label>Passwort bestätigen</label>
<input type="password" name="admin_password_confirm" required> <input type="password" name="admin_password_confirm" required>
</div> </div>
<button type="submit" class="btn btn-primary btn-lg btn-block">Weiter <i class="fas fa-arrow-right"></i></button> <button type="submit" class="btn btn-primary btn-lg btn-block">Weiter <i class="fas fa-arrow-right" aria-hidden="true"></i></button>
</form> </form>
<?php elseif ($step === 3): ?> <?php elseif ($step === 3): ?>
<form method="post"> <form method="post">
<input type="hidden" name="step" value="4"> <input type="hidden" name="step" value="4">
<h3>Schritt 3: Allgemeine Einstellungen</h3> <h3>Schritt 3: Allgemeine Einstellungen</h3>
<div class="form-group"> <div class="form-group">
<label>Anwendungs-Titel</label> <label>Anwendungs-Titel</label>
<input type="text" name="app_title" value="UniFi Voucher System" required> <input type="text" name="app_title" value="UniFi Voucher System" required>
</div> </div>
<div class="form-group"> <div class="form-group">
<label>Logo-URL (optional)</label> <label>Logo-URL (optional)</label>
<input type="text" name="logo_url" placeholder="https://example.com/logo.png"> <input type="text" name="logo_url" placeholder="https://example.com/logo.png">
</div> </div>
<div class="form-group"> <div class="form-group">
<label>Anleitung Überschrift</label> <label>Anleitung Überschrift</label>
<input type="text" name="instruction_header" value="So verwenden Sie Ihren Code"> <input type="text" name="instruction_header" value="So verwenden Sie Ihren Code">
</div> </div>
<div class="form-group"> <div class="form-group">
<label>Anleitung Text</label> <label>Anleitung Text</label>
<textarea name="instruction_text">Verbinden Sie sich mit dem WLAN und geben Sie den Code auf der Anmeldeseite ein.</textarea> <textarea name="instruction_text">Verbinden Sie sich mit dem WLAN und geben Sie den Code auf der Anmeldeseite ein.</textarea>
</div> </div>
<div class="form-group checkbox-group"> <div class="form-group checkbox-group">
<input type="checkbox" name="public_access" id="public_access"> <input type="checkbox" name="public_access" id="public_access">
<label for="public_access" style="margin: 0;">Öffentlicher Zugriff auf Code-Erstellung</label> <label for="public_access" style="margin: 0;">Öffentlicher Zugriff auf Code-Erstellung</label>
</div> </div>
<div class="section"> <div class="section">
<h3>Microsoft 365 Login (Optional)</h3> <h3>Microsoft 365 Login (Optional)</h3>
<div class="form-group"> <div class="form-group">
<label>Client ID</label> <label>Client ID</label>
<input type="text" name="m365_client_id"> <input type="text" name="m365_client_id">
</div> </div>
<div class="form-group"> <div class="form-group">
<label>Client Secret</label> <label>Client Secret</label>
<input type="password" name="m365_client_secret"> <input type="password" name="m365_client_secret">
</div> </div>
<div class="form-group"> <div class="form-group">
<label>Tenant ID</label> <label>Tenant ID</label>
<input type="text" name="m365_tenant_id"> <input type="text" name="m365_tenant_id">
</div> </div>
<div class="help-text">Leer lassen, wenn M365-Login nicht verwendet werden soll</div> <div class="help-text">Leer lassen, wenn M365-Login nicht verwendet werden soll</div>
</div> </div>
<button type="submit" class="btn btn-primary btn-lg btn-block">Weiter <i class="fas fa-arrow-right"></i></button> <button type="submit" class="btn btn-primary btn-lg btn-block">Weiter <i class="fas fa-arrow-right" aria-hidden="true"></i></button>
</form> </form>
<?php elseif ($step === 4): ?> <?php elseif ($step === 4): ?>
<form method="post"> <form method="post">
<input type="hidden" name="step" value="5"> <input type="hidden" name="step" value="5">
<h3>Schritt 4: Installation abschließen</h3> <h3>Schritt 4: Installation abschließen</h3>
<p style="margin-bottom: 20px; color: #666;"> <p style="margin-bottom: 20px; color: #666;">
Klicken Sie auf "Installation abschließen", um die Einrichtung zu beenden. Klicken Sie auf "Installation abschließen", um die Einrichtung zu beenden.
Die Datenbank und alle notwendigen Dateien werden erstellt. Die Datenbank und alle notwendigen Dateien werden erstellt.
</p> </p>
<button type="submit" class="btn btn-primary btn-lg btn-block">Installation abschließen</button> <button type="submit" class="btn btn-primary btn-lg btn-block">Installation abschließen</button>
</form> </form>
<?php endif; ?> <?php endif; ?>
</div> </div>
</body> </body>
</html> </html>

View file

@ -455,6 +455,15 @@ return [
'audit_action_template_updated' => 'Profil geändert', 'audit_action_template_updated' => 'Profil geändert',
'audit_action_template_deleted' => 'Profil gelöscht', 'audit_action_template_deleted' => 'Profil gelöscht',
'audit_page_info' => 'Seite {page} von {pages} ({total} Einträge)', 'audit_page_info' => 'Seite {page} von {pages} ({total} Einträge)',
'a11y_skip' => 'Zum Inhalt springen',
'a11y_theme' => 'Darstellung umschalten (hell/dunkel)',
'a11y_menu' => 'Navigation öffnen',
'a11y_language' => 'Sprache',
'a11y_notifications' => 'Benachrichtigungen',
'audit_system_anon' => 'System/Anonym',
'vouchers_resend' => 'Per E-Mail senden',
'label_minutes_short' => 'Min.',
'js_copy' => 'Kopieren',
'settings_tab_general' => 'Allgemein', 'settings_tab_general' => 'Allgemein',
'settings_tab_defaults' => 'Voucher-Standards', 'settings_tab_defaults' => 'Voucher-Standards',
'settings_tab_cron' => 'Cron-Sync', 'settings_tab_cron' => 'Cron-Sync',

View file

@ -455,6 +455,15 @@ return [
'audit_action_template_updated' => 'Profile updated', 'audit_action_template_updated' => 'Profile updated',
'audit_action_template_deleted' => 'Profile deleted', 'audit_action_template_deleted' => 'Profile deleted',
'audit_page_info' => 'Page {page} of {pages} ({total} entries)', 'audit_page_info' => 'Page {page} of {pages} ({total} entries)',
'a11y_skip' => 'Skip to content',
'a11y_theme' => 'Toggle appearance (light/dark)',
'a11y_menu' => 'Open navigation',
'a11y_language' => 'Language',
'a11y_notifications' => 'Notifications',
'audit_system_anon' => 'System/anonymous',
'vouchers_resend' => 'Send by email',
'label_minutes_short' => 'min',
'js_copy' => 'Copy',
'settings_tab_general' => 'General', 'settings_tab_general' => 'General',
'settings_tab_defaults' => 'Voucher Defaults', 'settings_tab_defaults' => 'Voucher Defaults',
'settings_tab_cron' => 'Cron Sync', 'settings_tab_cron' => 'Cron Sync',

View file

@ -169,7 +169,7 @@ try {
<?php if ($loginLogo): ?> <?php if ($loginLogo): ?>
<img src="<?= htmlspecialchars(Ui::mediaUrl($loginLogo)) ?>" alt="<?= htmlspecialchars($loginBrand) ?>" class="auth-brand-logo"> <img src="<?= htmlspecialchars(Ui::mediaUrl($loginLogo)) ?>" alt="<?= htmlspecialchars($loginBrand) ?>" class="auth-brand-logo">
<?php else: ?> <?php else: ?>
<span class="brand-mark"><i class="fas fa-wifi"></i></span> <span class="brand-mark"><i class="fas fa-wifi" aria-hidden="true"></i></span>
<span><?= htmlspecialchars($loginBrand) ?></span> <span><?= htmlspecialchars($loginBrand) ?></span>
<?php endif; ?> <?php endif; ?>
</div> </div>
@ -179,7 +179,7 @@ try {
<?php if (!empty($loginFeatures)): ?> <?php if (!empty($loginFeatures)): ?>
<ul class="auth-features"> <ul class="auth-features">
<?php foreach ($loginFeatures as $feature): ?> <?php foreach ($loginFeatures as $feature): ?>
<li><span class="tick"><i class="fas fa-check"></i></span> <?= htmlspecialchars($feature) ?></li> <li><span class="tick"><i class="fas fa-check" aria-hidden="true"></i></span> <?= htmlspecialchars($feature) ?></li>
<?php endforeach; ?> <?php endforeach; ?>
</ul> </ul>
<?php endif; ?> <?php endif; ?>
@ -190,14 +190,14 @@ try {
<section class="auth-panel"> <section class="auth-panel">
<div class="auth-tools"> <div class="auth-tools">
<div class="lang-switcher"> <div class="lang-switcher" role="group" aria-label="<?= __('a11y_language') ?>">
<?php foreach (I18n::getAvailable() as $code => $label): ?> <?php foreach (I18n::getAvailable() as $code => $label): ?>
<button class="lang-btn <?= I18n::getLanguage() === $code ? 'active' : '' ?>" <button class="lang-btn <?= I18n::getLanguage() === $code ? 'active' : '' ?>"
onclick="switchLanguage('<?= $code ?>')"><?= strtoupper($code) ?></button> onclick="switchLanguage('<?= $code ?>')"><?= strtoupper($code) ?></button>
<?php endforeach; ?> <?php endforeach; ?>
</div> </div>
<button id="darkModeBtn" class="dark-mode-toggle" onclick="toggleDarkMode()" title="Dark Mode"> <button id="darkModeBtn" class="dark-mode-toggle" onclick="toggleDarkMode()" aria-label="<?= __('a11y_theme') ?>" title="<?= __('a11y_theme') ?>">
<i class="fas fa-moon"></i> <i class="fas fa-moon" aria-hidden="true"></i>
</button> </button>
</div> </div>
@ -207,7 +207,7 @@ try {
<img src="<?= htmlspecialchars(Ui::mediaUrl($loginLogo)) ?>" alt="<?= htmlspecialchars($loginBrand) ?>" class="logo"> <img src="<?= htmlspecialchars(Ui::mediaUrl($loginLogo)) ?>" alt="<?= htmlspecialchars($loginBrand) ?>" class="logo">
<?php else: ?> <?php else: ?>
<div class="auth-mark"> <div class="auth-mark">
<span class="brand-mark"><i class="fas fa-wifi"></i></span> <span class="brand-mark"><i class="fas fa-wifi" aria-hidden="true"></i></span>
<span><?= htmlspecialchars($loginBrand) ?></span> <span><?= htmlspecialchars($loginBrand) ?></span>
</div> </div>
<?php endif; ?> <?php endif; ?>
@ -281,12 +281,12 @@ try {
<?php if (!$show2fa && $oidcEnabled): ?> <?php if (!$show2fa && $oidcEnabled): ?>
<div class="divider"><span><?= __('or') ?></span></div> <div class="divider"><span><?= __('or') ?></span></div>
<a href="<?= htmlspecialchars($oidcLoginUrl) ?>" class="btn btn-secondary btn-lg"> <a href="<?= htmlspecialchars($oidcLoginUrl) ?>" class="btn btn-secondary btn-lg">
<i class="fas fa-key"></i> <?= htmlspecialchars($oidcName) ?> <i class="fas fa-key" aria-hidden="true"></i> <?= htmlspecialchars($oidcName) ?>
</a> </a>
<?php endif; ?> <?php endif; ?>
<?php if ($publicAccess): ?> <?php if ($publicAccess): ?>
<div class="auth-links"><a href="index.php" class="back-link"><i class="fas fa-arrow-left"></i> <?= __('login_back') ?></a></div> <div class="auth-links"><a href="index.php" class="back-link"><i class="fas fa-arrow-left" aria-hidden="true"></i> <?= __('login_back') ?></a></div>
<?php endif; ?> <?php endif; ?>
</div> </div>
</section> </section>

View file

@ -166,12 +166,12 @@ try {
<?php if ($m365Enabled): ?> <?php if ($m365Enabled): ?>
<div class="divider"><span>oder</span></div> <div class="divider"><span>oder</span></div>
<a href="<?= htmlspecialchars($m365LoginUrl) ?>" class="btn btn-microsoft"> <a href="<?= htmlspecialchars($m365LoginUrl) ?>" class="btn btn-microsoft">
<i class="fab fa-microsoft"></i> Mit Microsoft 365 anmelden <i class="fab fa-microsoft" aria-hidden="true"></i> Mit Microsoft 365 anmelden
</a> </a>
<?php endif; ?> <?php endif; ?>
<?php if ($publicAccess): ?> <?php if ($publicAccess): ?>
<div class="auth-links"><a href="index.php" class="back-link"><i class="fas fa-arrow-left"></i> Zurück zur Code-Erstellung</a></div> <div class="auth-links"><a href="index.php" class="back-link"><i class="fas fa-arrow-left" aria-hidden="true"></i> Zurück zur Code-Erstellung</a></div>
<?php endif; ?> <?php endif; ?>
<!-- Debug Info (kann nach erfolgreicher Einrichtung entfernt werden) --> <!-- Debug Info (kann nach erfolgreicher Einrichtung entfernt werden) -->

View file

@ -83,7 +83,7 @@ if ($valid && $_SERVER['REQUEST_METHOD'] === 'POST') {
<?php if ($logoUrl): ?> <?php if ($logoUrl): ?>
<img src="<?= htmlspecialchars(Ui::mediaUrl($logoUrl)) ?>" alt="Logo" class="logo"> <img src="<?= htmlspecialchars(Ui::mediaUrl($logoUrl)) ?>" alt="Logo" class="logo">
<?php else: ?> <?php else: ?>
<div class="focus-icon" style="margin-bottom:14px;"><i class="fas fa-key"></i></div> <div class="focus-icon" style="margin-bottom:14px;"><i class="fas fa-key" aria-hidden="true"></i></div>
<?php endif; ?> <?php endif; ?>
<h1><?= __('reset_new_pw') ?></h1> <h1><?= __('reset_new_pw') ?></h1>

View file

@ -61,13 +61,13 @@ $channels = \Updater\UpdateManager::CHANNELS;
<div class="wrap"> <div class="wrap">
<div class="update-head"> <div class="update-head">
<div class="title"> <div class="title">
<span class="focus-icon"><i class="fas fa-rotate"></i></span> <span class="focus-icon"><i class="fas fa-rotate" aria-hidden="true"></i></span>
<div> <div>
<h1 style="font-size:20px;">System-Update</h1> <h1 style="font-size:20px;">System-Update</h1>
<p class="sub">Aktualisierung aus dem gewählten Release-Channel</p> <p class="sub">Aktualisierung aus dem gewählten Release-Channel</p>
</div> </div>
</div> </div>
<a href="/admin/" class="btn btn-secondary"><i class="fas fa-arrow-left"></i> Administration</a> <a href="/admin/" class="btn btn-secondary"><i class="fas fa-arrow-left" aria-hidden="true"></i> Administration</a>
</div> </div>
<div class="tabs"> <div class="tabs">
@ -93,8 +93,8 @@ $channels = \Updater\UpdateManager::CHANNELS;
<div class="card"> <div class="card">
<h2>Updates</h2> <h2>Updates</h2>
<button class="btn btn-primary" id="btnCheck"><i class="fas fa-magnifying-glass"></i> Auf Updates prüfen</button> <button class="btn btn-primary" id="btnCheck"><i class="fas fa-magnifying-glass" aria-hidden="true"></i> Auf Updates prüfen</button>
<button class="btn btn-success" id="btnInstall" style="display:none;margin-left:8px;"><i class="fas fa-download"></i> Update installieren</button> <button class="btn btn-success" id="btnInstall" style="display:none;margin-left:8px;"><i class="fas fa-download" aria-hidden="true"></i> Update installieren</button>
<div class="changelog" id="changelog"></div> <div class="changelog" id="changelog"></div>
@ -150,7 +150,7 @@ $('channel').addEventListener('change', async (e) => {
// Auf Updates pruefen // Auf Updates pruefen
$('btnCheck').addEventListener('click', async () => { $('btnCheck').addEventListener('click', async () => {
clearAlerts(); clearAlerts();
$('btnCheck').disabled = true; $('btnCheck').innerHTML = '<i class="fas fa-circle-notch fa-spin"></i> Prüfe …'; $('btnCheck').disabled = true; $('btnCheck').innerHTML = '<i class="fas fa-circle-notch fa-spin" aria-hidden="true"></i> Prüfe …';
try { try {
const r = await fetch('update.php?action=check'); const r = await fetch('update.php?action=check');
const d = await r.json(); const d = await r.json();
@ -172,7 +172,7 @@ $('btnCheck').addEventListener('click', async () => {
} catch (e) { } catch (e) {
showAlert('error', 'Prüfung fehlgeschlagen: ' + e.message); showAlert('error', 'Prüfung fehlgeschlagen: ' + e.message);
} finally { } finally {
$('btnCheck').disabled = false; $('btnCheck').innerHTML = '<i class="fas fa-magnifying-glass"></i> Auf Updates prüfen'; $('btnCheck').disabled = false; $('btnCheck').innerHTML = '<i class="fas fa-magnifying-glass" aria-hidden="true"></i> Auf Updates prüfen';
} }
}); });