Apply modern SaaS design to all admin pages

Modernize all admin interface pages with consistent design language:
- Add hero sections with gradient backgrounds and blur effects
- Implement modern card designs with hover animations
- Use gradient buttons with shadow effects
- Add emoji icons in colored containers
- Apply consistent color themes per page
- Enhance user experience with smooth transitions

Pages updated:
- /admin/devices (purple theme)
- /admin/mqtt (cyan/blue theme)
- /admin/setup (emerald theme)
- /admin/users (violet theme)
- /admin/settings (indigo theme)
- /admin/emails (pink/rose theme)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-11-29 23:31:25 +00:00
parent 5f637817ce
commit a39b53151e
6 changed files with 392 additions and 243 deletions

View File

@@ -341,39 +341,66 @@ export default function MqttPage() {
);
return (
<div className="p-8">
<div className="flex justify-between items-center mb-6">
<h1 className="text-3xl font-bold">MQTT Provisioning</h1>
<div className="flex gap-4 items-center">
{syncStatus && syncStatus.pending_changes > 0 && (
<span className="px-3 py-1 bg-yellow-100 text-yellow-800 rounded-md text-sm">
{syncStatus.pending_changes} ausstehende Änderungen
</span>
)}
<button
onClick={handleSync}
disabled={syncing || !syncStatus || syncStatus.pending_changes === 0}
className="px-4 py-2 bg-green-600 text-white rounded-md hover:bg-green-700 disabled:opacity-50 disabled:cursor-not-allowed"
>
{syncing ? "Synchronisiere..." : "MQTT Sync"}
</button>
<button
onClick={() => setShowAddModal(true)}
className="px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700"
>
Device Provisionieren
</button>
<div className="space-y-8">
{/* Hero Section with Gradient */}
<div className="relative overflow-hidden rounded-2xl bg-gradient-to-br from-cyan-600 via-blue-700 to-indigo-800 p-8 shadow-xl">
<div className="absolute top-0 right-0 -mt-4 -mr-4 h-40 w-40 rounded-full bg-white/10 blur-3xl"></div>
<div className="absolute bottom-0 left-0 -mb-4 -ml-4 h-40 w-40 rounded-full bg-white/10 blur-3xl"></div>
<div className="relative">
<div className="flex justify-between items-center mb-4">
<div>
<h2 className="text-4xl font-bold text-white mb-2">MQTT Provisioning</h2>
<p className="text-cyan-100 text-lg">Verwalte MQTT-Zugangsdaten und Berechtigungen</p>
</div>
<div className="flex gap-3">
{syncStatus && syncStatus.pending_changes > 0 && (
<span className="px-4 py-2 bg-yellow-400 text-yellow-900 rounded-lg text-sm font-bold shadow-lg">
{syncStatus.pending_changes} ausstehende Änderungen
</span>
)}
<button
onClick={handleSync}
disabled={syncing || !syncStatus || syncStatus.pending_changes === 0}
className="px-5 py-2.5 bg-gradient-to-r from-green-500 to-emerald-600 text-white rounded-xl hover:from-green-600 hover:to-emerald-700 disabled:from-gray-400 disabled:to-gray-500 font-semibold shadow-lg hover:shadow-xl transition-all"
>
{syncing ? "Synchronisiere..." : "🔄 MQTT Sync"}
</button>
<button
onClick={() => setShowAddModal(true)}
className="px-5 py-2.5 bg-white text-cyan-700 rounded-xl hover:bg-cyan-50 font-semibold shadow-lg hover:shadow-xl transition-all"
>
+ Device Provisionieren
</button>
</div>
</div>
</div>
</div>
{syncStatus && (
<div className="mb-6 p-4 bg-gray-100 rounded-md">
<h3 className="font-semibold mb-2">Sync Status</h3>
<div className="text-sm">
<p>Status: <span className={syncStatus.last_sync_status === 'success' ? 'text-green-600' : 'text-red-600'}>{syncStatus.last_sync_status}</span></p>
{syncStatus.last_sync_at && (
<p>Letzter Sync: {new Date(syncStatus.last_sync_at).toLocaleString('de-DE')}</p>
)}
<div className="bg-white rounded-2xl shadow-lg overflow-hidden">
<div className="bg-gradient-to-r from-slate-50 to-gray-50 px-6 py-4 border-b border-gray-200">
<div className="flex items-center gap-3">
<div className="w-10 h-10 rounded-lg bg-gradient-to-br from-slate-600 to-slate-700 flex items-center justify-center text-white text-xl">
🔄
</div>
<h3 className="text-xl font-bold text-gray-900">Sync Status</h3>
</div>
</div>
<div className="p-6">
<div className="grid grid-cols-2 gap-4">
<div className="bg-gradient-to-br from-blue-50 to-indigo-50 p-4 rounded-xl border border-blue-100">
<p className="text-sm font-semibold text-blue-700 uppercase tracking-wide mb-1">Status</p>
<p className={`text-2xl font-bold ${syncStatus.last_sync_status === 'success' ? 'text-green-600' : 'text-red-600'}`}>
{syncStatus.last_sync_status === 'success' ? '✓ Erfolgreich' : '✗ Fehler'}
</p>
</div>
{syncStatus.last_sync_at && (
<div className="bg-gradient-to-br from-purple-50 to-violet-50 p-4 rounded-xl border border-purple-100">
<p className="text-sm font-semibold text-purple-700 uppercase tracking-wide mb-1">Letzter Sync</p>
<p className="text-lg font-bold text-purple-900">{new Date(syncStatus.last_sync_at).toLocaleString('de-DE')}</p>
</div>
)}
</div>
</div>
</div>
)}
@@ -383,26 +410,48 @@ export default function MqttPage() {
const deviceRules = aclRules[cred.device_id] || [];
return (
<div key={cred.id} className="border rounded-lg p-6 bg-white shadow-sm">
<div className="flex justify-between items-start mb-4">
<div>
<h3 className="text-xl font-semibold">{cred.device_name}</h3>
<p className="text-sm text-gray-500">Device ID: {cred.device_id}</p>
<p className="text-sm text-gray-600 mt-1">Username: <code className="bg-gray-100 px-2 py-1 rounded">{cred.mqtt_username}</code></p>
<p className="text-xs text-gray-500 mt-1">Erstellt: {new Date(cred.created_at).toLocaleString('de-DE')}</p>
<div key={cred.id} className="bg-white rounded-2xl shadow-lg overflow-hidden hover:shadow-2xl transition-shadow">
<div className="bg-gradient-to-r from-blue-50 to-indigo-50 px-6 py-5 border-b border-blue-100">
<div className="flex justify-between items-start">
<div className="flex items-center gap-3">
<div className="w-12 h-12 rounded-xl bg-gradient-to-br from-blue-600 to-indigo-600 flex items-center justify-center text-white text-2xl shadow-lg">
📱
</div>
<div>
<h3 className="text-xl font-bold text-gray-900">{cred.device_name}</h3>
<p className="text-sm text-gray-600">Device ID: <code className="bg-white px-2 py-0.5 rounded font-mono text-xs">{cred.device_id}</code></p>
</div>
</div>
<div className="flex gap-2">
<button
onClick={() => handleToggleEnabled(cred.device_id, !cred.enabled)}
className={`px-4 py-2 rounded-lg text-sm font-bold shadow-md ${cred.enabled ? 'bg-gradient-to-r from-green-500 to-emerald-600 text-white' : 'bg-gradient-to-r from-red-500 to-red-600 text-white'}`}
>
{cred.enabled ? '✓ Aktiviert' : '✗ Deaktiviert'}
</button>
</div>
</div>
<div className="flex gap-2">
<button
onClick={() => handleToggleEnabled(cred.device_id, !cred.enabled)}
className={`px-3 py-1 rounded text-sm ${cred.enabled ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800'}`}
>
{cred.enabled ? 'Aktiviert' : 'Deaktiviert'}
</button>
</div>
<div className="p-6">
<div className="bg-gradient-to-br from-gray-50 to-slate-50 rounded-xl p-4 mb-4 border border-gray-200">
<div className="grid grid-cols-2 gap-4">
<div>
<p className="text-xs font-semibold text-gray-500 uppercase tracking-wide mb-1">Username</p>
<code className="text-sm font-mono text-gray-900 bg-white px-3 py-1.5 rounded-lg border border-gray-200 inline-block">{cred.mqtt_username}</code>
</div>
<div>
<p className="text-xs font-semibold text-gray-500 uppercase tracking-wide mb-1">Erstellt</p>
<p className="text-sm text-gray-700">{new Date(cred.created_at).toLocaleString('de-DE')}</p>
</div>
</div>
</div>
<div className="flex gap-2 mb-4">
<button
onClick={() => handleRegeneratePassword(cred.device_id)}
className="px-3 py-1 bg-yellow-100 text-yellow-800 rounded text-sm hover:bg-yellow-200"
className="flex-1 px-4 py-2.5 bg-gradient-to-r from-yellow-500 to-orange-500 text-white rounded-lg hover:from-yellow-600 hover:to-orange-600 text-sm font-semibold shadow-md hover:shadow-lg transition-all"
>
Passwort Reset
🔑 Passwort Reset
</button>
<button
onClick={() => {
@@ -414,60 +463,61 @@ export default function MqttPage() {
});
setShowAclModal(true);
}}
className="px-3 py-1 bg-blue-100 text-blue-800 rounded text-sm hover:bg-blue-200"
className="flex-1 px-4 py-2.5 bg-gradient-to-r from-blue-600 to-blue-700 text-white rounded-lg hover:from-blue-700 hover:to-blue-800 text-sm font-semibold shadow-md hover:shadow-lg transition-all"
>
ACL Hinzufügen
+ ACL Hinzufügen
</button>
<button
onClick={() => handleDeleteCredentials(cred.device_id)}
className="px-3 py-1 bg-red-100 text-red-800 rounded text-sm hover:bg-red-200"
className="px-4 py-2.5 bg-gradient-to-r from-red-600 to-red-700 text-white rounded-lg hover:from-red-700 hover:to-red-800 text-sm font-semibold shadow-md hover:shadow-lg transition-all"
>
Löschen
🗑 Löschen
</button>
</div>
</div>
<div className="mt-4">
<h4 className="font-semibold mb-2 text-sm">ACL Regeln:</h4>
<div>
<h4 className="font-bold mb-3 text-gray-900 flex items-center gap-2">
<span className="text-lg">🔐</span>
ACL Regeln
</h4>
{deviceRules.length > 0 ? (
<table className="w-full text-sm">
<thead className="bg-gray-50">
<tr>
<th className="px-4 py-2 text-left">Topic Pattern</th>
<th className="px-4 py-2 text-left">Berechtigung</th>
<th className="px-4 py-2 text-left">Erstellt</th>
<th className="px-4 py-2"></th>
</tr>
</thead>
<tbody>
{deviceRules.map(rule => (
<tr key={rule.id} className="border-t">
<td className="px-4 py-2"><code className="bg-gray-100 px-2 py-1 rounded">{rule.topic_pattern}</code></td>
<td className="px-4 py-2"><span className="px-2 py-1 bg-blue-100 text-blue-800 rounded text-xs">{rule.permission}</span></td>
<td className="px-4 py-2 text-gray-500">{new Date(rule.created_at).toLocaleString('de-DE')}</td>
<td className="px-4 py-2">
<button
onClick={() => handleDeleteAclRule(rule.id, cred.device_id)}
className="text-red-600 hover:text-red-800 text-xs"
>
Löschen
</button>
</td>
</tr>
))}
</tbody>
</table>
<div className="space-y-2">
{deviceRules.map(rule => (
<div key={rule.id} className="bg-gradient-to-br from-gray-50 to-slate-50 rounded-lg p-4 border border-gray-200 hover:border-blue-300 transition-colors">
<div className="flex items-center justify-between">
<div className="flex-1">
<div className="flex items-center gap-3 mb-2">
<code className="text-sm font-mono bg-white px-3 py-1.5 rounded-lg border border-gray-200">{rule.topic_pattern}</code>
<span className="px-3 py-1 bg-gradient-to-r from-blue-500 to-indigo-600 text-white rounded-lg text-xs font-bold">{rule.permission}</span>
</div>
<p className="text-xs text-gray-500">Erstellt: {new Date(rule.created_at).toLocaleString('de-DE')}</p>
</div>
<button
onClick={() => handleDeleteAclRule(rule.id, cred.device_id)}
className="ml-4 px-3 py-2 bg-red-100 text-red-700 rounded-lg hover:bg-red-200 text-xs font-semibold"
>
Löschen
</button>
</div>
</div>
))}
</div>
) : (
<p className="text-sm text-gray-500">Keine ACL Regeln definiert</p>
<div className="bg-gradient-to-br from-gray-50 to-slate-50 rounded-lg p-6 text-center border border-gray-200">
<p className="text-sm text-gray-500">Keine ACL Regeln definiert</p>
</div>
)}
</div>
</div>
</div>
);
})}
{credentials.length === 0 && (
<div className="text-center py-12 text-gray-500">
Noch keine Devices provisioniert
<div className="relative overflow-hidden bg-gradient-to-br from-gray-50 to-slate-50 rounded-2xl shadow-lg p-12 text-center border border-gray-200">
<div className="absolute top-0 right-0 text-9xl opacity-5">🔐</div>
<p className="text-xl font-semibold text-gray-600 mb-2">Noch keine Devices provisioniert</p>
<p className="text-gray-500">Erstelle MQTT-Credentials für dein erstes Device.</p>
</div>
)}
</div>