Frontend: React+Vite+Tailwind, Login, Dashboard, Agents, Customers, Settings, Dark Theme, Nginx auf .20
This commit is contained in:
@@ -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>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user