Frontend: React+Vite+Tailwind, Login, Dashboard, Agents, Customers, Settings, Dark Theme, Nginx auf .20

This commit is contained in:
cynfo3000
2026-02-28 15:40:07 +01:00
parent c6c8257cf0
commit c56fded7d7
24 changed files with 5548 additions and 0 deletions
+127
View File
@@ -0,0 +1,127 @@
import { useState } from 'react'
import { Link, Outlet, useLocation, useNavigate } from 'react-router-dom'
import { useAuthStore } from '../stores/auth'
import {
LayoutDashboard,
Server,
Users,
Shield,
Settings,
LogOut,
Menu,
X,
} from 'lucide-react'
const navItems = [
{ path: '/', label: 'Dashboard', icon: LayoutDashboard },
{ path: '/agents', label: 'Firewalls', icon: Server },
{ path: '/customers', label: 'Kunden', icon: Users },
{ path: '/settings', label: 'Einstellungen', icon: Settings },
]
export default function AppLayout() {
const [sidebarOpen, setSidebarOpen] = useState(false)
const location = useLocation()
const navigate = useNavigate()
const { user, logout } = useAuthStore()
const handleLogout = () => {
logout()
navigate('/login')
}
return (
<div className="flex h-screen overflow-hidden">
{/* Sidebar */}
<aside
className={`fixed inset-y-0 left-0 z-50 w-56 bg-gray-900 border-r border-gray-800 transform transition-transform lg:translate-x-0 lg:static lg:flex-shrink-0 ${
sidebarOpen ? 'translate-x-0' : '-translate-x-full'
}`}
>
<div className="flex flex-col h-full">
{/* Logo */}
<div className="flex items-center gap-2 px-4 py-4 border-b border-gray-800">
<Shield className="w-6 h-6 text-orange-500" />
<span className="text-lg font-bold text-white">RMM</span>
<button
className="ml-auto lg:hidden text-gray-400 hover:text-white"
onClick={() => setSidebarOpen(false)}
>
<X className="w-5 h-5" />
</button>
</div>
{/* Nav */}
<nav className="flex-1 px-2 py-3 space-y-1">
{navItems.map((item) => {
const Icon = item.icon
const active =
item.path === '/'
? location.pathname === '/'
: location.pathname.startsWith(item.path)
return (
<Link
key={item.path}
to={item.path}
onClick={() => setSidebarOpen(false)}
className={`flex items-center gap-3 px-3 py-2 rounded text-sm transition-colors ${
active
? 'bg-gray-800 text-orange-400'
: 'text-gray-400 hover:bg-gray-800/50 hover:text-gray-200'
}`}
>
<Icon className="w-4 h-4" />
{item.label}
</Link>
)
})}
</nav>
{/* User */}
<div className="px-3 py-3 border-t border-gray-800">
<div className="flex items-center justify-between">
<span className="text-sm text-gray-400 truncate">
{user?.display_name || user?.username || 'Admin'}
</span>
<button
onClick={handleLogout}
className="text-gray-500 hover:text-red-400 transition-colors"
title="Abmelden"
>
<LogOut className="w-4 h-4" />
</button>
</div>
</div>
</div>
</aside>
{/* Overlay */}
{sidebarOpen && (
<div
className="fixed inset-0 z-40 bg-black/50 lg:hidden"
onClick={() => setSidebarOpen(false)}
/>
)}
{/* Main */}
<div className="flex-1 flex flex-col overflow-hidden">
{/* Top bar (mobile) */}
<header className="lg:hidden flex items-center gap-3 px-4 py-3 bg-gray-900 border-b border-gray-800">
<button
onClick={() => setSidebarOpen(true)}
className="text-gray-400 hover:text-white"
>
<Menu className="w-5 h-5" />
</button>
<Shield className="w-5 h-5 text-orange-500" />
<span className="font-bold text-white">RMM</span>
</header>
{/* Content */}
<main className="flex-1 overflow-y-auto p-4 lg:p-6">
<Outlet />
</main>
</div>
</div>
)
}