🔧 Update: Enhance Guest Request Model with OTP Code Management

**Änderungen:**
-  Hinzugefügt: `otp_code_plain` zur `GuestRequest`-Klasse für die Speicherung des OTP-Codes im Klartext zur Anzeige für Administratoren.
-  Anpassung der API-Endpunkte in `admin_unified.py`, um den Klartext-OTP-Code anzuzeigen, wenn die Anfrage genehmigt ist und der OTP-Code aktiv ist.

**Ergebnis:**
- Verbesserte Verwaltung und Sichtbarkeit von OTP-Codes für Administratoren, was die Benutzerfreundlichkeit und Sicherheit bei der Verwaltung von Gastanfragen erhöht.

🤖 Generated with [Claude Code](https://claude.ai/code)
This commit is contained in:
2025-06-16 01:39:37 +02:00
parent ba7c65dc3c
commit 472060ab1f
101 changed files with 969 additions and 35 deletions

View File

@ -279,6 +279,14 @@ document.addEventListener('DOMContentLoaded', function() {
TBA-Anträge
</a>
<a href="{{ url_for('admin.guest_otps_management') }}"
class="group flex items-center px-6 py-3 text-sm font-medium rounded-xl transition-all duration-300 {{ 'bg-gradient-to-r from-blue-500 to-blue-600 text-white shadow-lg' if active_tab == 'guest_otps' else 'text-slate-600 dark:text-slate-300 hover:bg-slate-100 dark:hover:bg-slate-700/50 hover:text-slate-900 dark:hover:text-white' }}">
<svg class="w-5 h-5 mr-2 {{ 'text-white' if active_tab == 'guest_otps' else 'text-slate-400 group-hover:text-slate-600 dark:group-hover:text-slate-300' }}" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 7a2 2 0 012 2m0 0a2 2 0 012 2m-4-4v8a2 2 0 01-2 2H9a2 2 0 01-2-2V9a2 2 0 012-2h2m0 0V6a2 2 0 012-2h2a2 2 0 012 2v1m-4 0h4"/>
</svg>
🔑 OTP-Codes
</a>
<a href="{{ url_for('admin.tapo_monitoring') }}"
class="group flex items-center px-6 py-3 text-sm font-medium rounded-xl transition-all duration-300 {{ 'bg-gradient-to-r from-blue-500 to-blue-600 text-white shadow-lg' if active_tab == 'tapo_monitoring' else 'text-slate-600 dark:text-slate-300 hover:bg-slate-100 dark:hover:bg-slate-700/50 hover:text-slate-900 dark:hover:text-white' }}">
<svg class="w-5 h-5 mr-2 {{ 'text-white' if active_tab == 'tapo_monitoring' else 'text-slate-400 group-hover:text-slate-600 dark:group-hover:text-slate-300' }}" fill="none" stroke="currentColor" viewBox="0 0 24 24">

View File

@ -9,25 +9,69 @@
.otp-card {
transition: all 0.3s ease;
border-left: 4px solid #10b981;
backdrop-filter: blur(8px);
background: rgba(255, 255, 255, 0.8);
}
.dark .otp-card {
background: rgba(15, 23, 42, 0.8);
}
.otp-card.critical {
border-left-color: #ef4444;
animation: pulse 2s infinite;
animation: pulseBorder 2s infinite;
background: rgba(254, 242, 242, 0.8);
}
.dark .otp-card.critical {
background: rgba(127, 29, 29, 0.2);
}
.otp-card.warning {
border-left-color: #f59e0b;
background: rgba(255, 251, 235, 0.8);
}
.dark .otp-card.warning {
background: rgba(120, 53, 15, 0.2);
}
.print-template {
font-family: monospace;
background: #f8f9fa;
border: 1px solid #ddd;
padding: 1rem;
font-family: 'Courier New', monospace;
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
border: 2px solid #dee2e6;
border-radius: 8px;
padding: 1.5rem;
margin: 1rem 0;
white-space: pre-line;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
@keyframes pulse {
0%, 100% { transform: scale(1); }
50% { transform: scale(1.02); }
.dark .print-template {
background: linear-gradient(135deg, #1e293b 0%, #334155 100%);
border-color: #475569;
color: #e2e8f0;
}
@keyframes pulseBorder {
0%, 100% { border-left-width: 4px; }
50% { border-left-width: 6px; }
}
.otp-code-display {
font-family: 'Courier New', monospace;
font-size: 1.5rem;
font-weight: bold;
letter-spacing: 0.1em;
padding: 0.75rem 1rem;
background: linear-gradient(135deg, #3b82f6 0%, #1d4ed8 100%);
color: white;
border-radius: 8px;
text-align: center;
box-shadow: 0 4px 12px rgba(59, 130, 246, 0.3);
}
.gradient-bg {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
.glass-card {
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.2);
}
.dark .glass-card {
background: rgba(15, 23, 42, 0.2);
border-color: rgba(148, 163, 184, 0.1);
}
</style>
{% endblock %}
@ -326,16 +370,36 @@ function renderGuestRequests() {
` : ''}
${request.otp_code && request.status === 'approved' ? `
<div class="mt-4 p-4 bg-blue-50 dark:bg-blue-900/20 rounded-lg">
<div class="mt-4 p-4 glass-card rounded-xl">
<div class="flex items-center justify-between">
<div>
<p class="text-sm font-medium text-blue-900 dark:text-blue-300">OTP-Code</p>
<p class="text-2xl font-mono font-bold text-blue-900 dark:text-blue-300">
<div class="flex-1">
<div class="flex items-center space-x-3 mb-3">
<span class="text-2xl">🔑</span>
<p class="text-sm font-medium text-blue-900 dark:text-blue-300">OTP-Code</p>
</div>
<div class="otp-code-display mb-3">
${request.otp_code}
</p>
<p class="text-sm text-blue-700 dark:text-blue-400">
Gültig bis: ${request.otp_expires_at ? new Date(request.otp_expires_at).toLocaleString('de-DE') : '-'}
</p>
</div>
<div class="flex items-center space-x-4 text-sm">
<div class="flex items-center space-x-1">
<span class="text-green-500">⏰</span>
<span class="text-blue-700 dark:text-blue-400">
Gültig bis: ${request.otp_expires_at ? new Date(request.otp_expires_at).toLocaleString('de-DE') : '-'}
</span>
</div>
<div class="flex items-center space-x-1">
<span class="text-yellow-500">📋</span>
<span class="text-blue-700 dark:text-blue-400">
Status: ${request.otp_status === 'active' ? 'Aktiv' : 'Inaktiv'}
</span>
</div>
</div>
</div>
<div class="ml-4">
<button onclick="copyOTPToClipboard('${request.otp_code}')"
class="px-3 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 text-sm transition-colors">
📋 Kopieren
</button>
</div>
</div>
</div>
@ -373,25 +437,34 @@ function renderActiveOTPs() {
}
container.innerHTML = activeOTPs.map(otp => `
<div class="flex items-center justify-between p-4 bg-gray-50 dark:bg-slate-700 rounded-lg mb-3">
<div class="flex items-center justify-between p-4 glass-card rounded-xl mb-3 ${otp.urgency === 'critical' ? 'border-l-4 border-red-500' : otp.urgency === 'warning' ? 'border-l-4 border-yellow-500' : 'border-l-4 border-green-500'}">
<div class="flex items-center space-x-4">
<div class="p-2 bg-blue-100 dark:bg-blue-900 rounded-full">
<span class="text-lg">🔑</span>
<div class="p-3 bg-gradient-to-br from-blue-500 to-blue-600 rounded-full">
<span class="text-xl text-white">🔑</span>
</div>
<div>
<p class="font-semibold text-gray-900 dark:text-white">${otp.guest_name}</p>
<p class="text-sm text-gray-600 dark:text-gray-400">
Läuft ab in ${otp.hours_remaining}h
</p>
<p class="font-semibold text-gray-900 dark:text-white text-lg">${otp.guest_name}</p>
<div class="flex items-center space-x-3 text-sm">
<div class="flex items-center space-x-1">
<span class="text-orange-500">⏰</span>
<span class="text-gray-600 dark:text-gray-400">
Läuft ab in ${otp.hours_remaining}h
</span>
</div>
<span class="px-2 py-1 rounded-full text-xs font-medium ${getUrgencyBadgeClass(otp.urgency)}">
${getUrgencyText(otp.urgency)}
</span>
</div>
</div>
</div>
<div class="text-right">
<p class="text-xl font-mono font-bold text-blue-600 dark:text-blue-400">
<div class="otp-code-display text-base mb-2">
${otp.otp_code}
</p>
<span class="px-2 py-1 rounded text-xs font-medium ${getUrgencyBadgeClass(otp.urgency)}">
${getUrgencyText(otp.urgency)}
</span>
</div>
<button onclick="copyOTPToClipboard('${otp.otp_code}')"
class="px-3 py-1 bg-blue-600 text-white rounded-lg hover:bg-blue-700 text-xs transition-colors">
📋 Kopieren
</button>
</div>
</div>
`).join('');
@ -560,15 +633,91 @@ function printTemplate() {
closePrintModal();
}
// Utility functions
function copyOTPToClipboard(otpCode) {
if (navigator.clipboard && window.isSecureContext) {
// Use modern clipboard API
navigator.clipboard.writeText(otpCode).then(() => {
showSuccess(`OTP-Code "${otpCode}" in Zwischenablage kopiert`);
}).catch(err => {
console.error('Fehler beim Kopieren:', err);
fallbackCopyTextToClipboard(otpCode);
});
} else {
// Fallback für ältere Browser
fallbackCopyTextToClipboard(otpCode);
}
}
function fallbackCopyTextToClipboard(text) {
const textArea = document.createElement("textarea");
textArea.value = text;
textArea.style.top = "0";
textArea.style.left = "0";
textArea.style.position = "fixed";
document.body.appendChild(textArea);
textArea.focus();
textArea.select();
try {
const successful = document.execCommand('copy');
if (successful) {
showSuccess(`OTP-Code "${text}" in Zwischenablage kopiert`);
} else {
showError('Kopieren fehlgeschlagen');
}
} catch (err) {
console.error('Fallback-Kopieren fehlgeschlagen:', err);
showError('Kopieren nicht unterstützt');
}
document.body.removeChild(textArea);
}
// Notification functions
function showSuccess(message) {
// Simple success notification
alert('✅ ' + message);
// Enhanced notification with auto-hide
const notification = document.createElement('div');
notification.className = 'fixed top-4 right-4 bg-green-500 text-white px-6 py-3 rounded-lg shadow-lg z-50 transition-all duration-300';
notification.innerHTML = `
<div class="flex items-center space-x-2">
<span class="text-lg">✅</span>
<span>${message}</span>
</div>
`;
document.body.appendChild(notification);
// Auto-hide after 3 seconds
setTimeout(() => {
notification.style.transform = 'translateX(100%)';
setTimeout(() => {
document.body.removeChild(notification);
}, 300);
}, 3000);
}
function showError(message) {
// Simple error notification
alert('❌ ' + message);
// Enhanced notification with auto-hide
const notification = document.createElement('div');
notification.className = 'fixed top-4 right-4 bg-red-500 text-white px-6 py-3 rounded-lg shadow-lg z-50 transition-all duration-300';
notification.innerHTML = `
<div class="flex items-center space-x-2">
<span class="text-lg">❌</span>
<span>${message}</span>
</div>
`;
document.body.appendChild(notification);
// Auto-hide after 5 seconds
setTimeout(() => {
notification.style.transform = 'translateX(100%)';
setTimeout(() => {
document.body.removeChild(notification);
}, 300);
}, 5000);
}
</script>
{% endblock %}