Created comprehensive VPN setup tooling for Peaceful Spirit L2TP/IPsec connection and enhanced agent documentation framework. VPN Configuration (PST-NW-VPN): - Setup-PST-L2TP-VPN.ps1: Automated L2TP/IPsec setup with split-tunnel and DNS - Connect-PST-VPN.ps1: Connection helper with PPP adapter detection, DNS (192.168.0.2), and route config (192.168.0.0/24) - Connect-PST-VPN-Standalone.ps1: Self-contained connection script for remote deployment - Fix-PST-VPN-Auth.ps1: Authentication troubleshooting for CHAP/MSChapv2 - Diagnose-VPN-Interface.ps1: Comprehensive VPN interface and routing diagnostic - Quick-Test-VPN.ps1: Fast connectivity verification (DNS/router/routes) - Add-PST-VPN-Route-Manual.ps1: Manual route configuration helper - vpn-connect.bat, vpn-disconnect.bat: Simple batch file shortcuts - OpenVPN config files (Windows-compatible, abandoned for L2TP) Key VPN Implementation Details: - L2TP creates PPP adapter with connection name as interface description - UniFi auto-configures DNS (192.168.0.2) but requires manual route to 192.168.0.0/24 - Split-tunnel enabled (only remote traffic through VPN) - All-user connection for pre-login auto-connect via scheduled task - Authentication: CHAP + MSChapv2 for UniFi compatibility Agent Documentation: - AGENT_QUICK_REFERENCE.md: Quick reference for all specialized agents - documentation-squire.md: Documentation and task management specialist agent - Updated all agent markdown files with standardized formatting Project Organization: - Moved conversation logs to dedicated directories (guru-connect-conversation-logs, guru-rmm-conversation-logs) - Cleaned up old session JSONL files from projects/msp-tools/ - Added guru-connect infrastructure (agent, dashboard, proto, scripts, .gitea workflows) - Added guru-rmm server components and deployment configs Technical Notes: - VPN IP pool: 192.168.4.x (client gets 192.168.4.6) - Remote network: 192.168.0.0/24 (router at 192.168.0.10) - PSK: rrClvnmUeXEFo90Ol+z7tfsAZHeSK6w7 - Credentials: pst-admin / 24Hearts$ Files: 15 VPN scripts, 2 agent docs, conversation log reorganization, guru-connect/guru-rmm infrastructure additions Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
113 lines
4.2 KiB
TypeScript
113 lines
4.2 KiB
TypeScript
import { ReactNode } from "react";
|
|
import { Link, useLocation, useNavigate } from "react-router-dom";
|
|
import { LayoutDashboard, Server, Terminal, Settings, LogOut, Menu, X, Building2, MapPin } from "lucide-react";
|
|
import { useState } from "react";
|
|
import { useAuth } from "../hooks/useAuth";
|
|
import { Button } from "./Button";
|
|
|
|
interface LayoutProps {
|
|
children: ReactNode;
|
|
}
|
|
|
|
const navItems = [
|
|
{ path: "/", label: "Dashboard", icon: LayoutDashboard },
|
|
{ path: "/clients", label: "Clients", icon: Building2 },
|
|
{ path: "/sites", label: "Sites", icon: MapPin },
|
|
{ path: "/agents", label: "Agents", icon: Server },
|
|
{ path: "/commands", label: "Commands", icon: Terminal },
|
|
{ path: "/settings", label: "Settings", icon: Settings },
|
|
];
|
|
|
|
export function Layout({ children }: LayoutProps) {
|
|
const [sidebarOpen, setSidebarOpen] = useState(false);
|
|
const location = useLocation();
|
|
const navigate = useNavigate();
|
|
const { user, logout } = useAuth();
|
|
|
|
const handleLogout = () => {
|
|
logout();
|
|
navigate("/login");
|
|
};
|
|
|
|
return (
|
|
<div className="min-h-screen bg-[hsl(var(--background))]">
|
|
{/* Mobile header */}
|
|
<div className="lg:hidden flex items-center justify-between p-4 border-b border-[hsl(var(--border))]">
|
|
<span className="font-bold text-lg">GuruRMM</span>
|
|
<Button variant="ghost" size="icon" onClick={() => setSidebarOpen(!sidebarOpen)}>
|
|
{sidebarOpen ? <X className="h-5 w-5" /> : <Menu className="h-5 w-5" />}
|
|
</Button>
|
|
</div>
|
|
|
|
<div className="flex">
|
|
{/* Sidebar */}
|
|
<aside
|
|
className={`fixed inset-y-0 left-0 z-50 w-64 bg-[hsl(var(--card))] border-r border-[hsl(var(--border))] transform transition-transform duration-200 lg:translate-x-0 lg:static ${
|
|
sidebarOpen ? "translate-x-0" : "-translate-x-full"
|
|
}`}
|
|
>
|
|
<div className="flex flex-col h-full">
|
|
<div className="p-6 hidden lg:block">
|
|
<h1 className="text-xl font-bold">GuruRMM</h1>
|
|
</div>
|
|
|
|
<nav className="flex-1 px-4 space-y-1 mt-4 lg:mt-0">
|
|
{navItems.map((item) => {
|
|
const isActive = location.pathname === item.path;
|
|
return (
|
|
<Link
|
|
key={item.path}
|
|
to={item.path}
|
|
onClick={() => setSidebarOpen(false)}
|
|
className={`flex items-center gap-3 px-3 py-2 rounded-md text-sm font-medium transition-colors ${
|
|
isActive
|
|
? "bg-[hsl(var(--primary))] text-[hsl(var(--primary-foreground))]"
|
|
: "text-[hsl(var(--foreground))] hover:bg-[hsl(var(--muted))]"
|
|
}`}
|
|
>
|
|
<item.icon className="h-5 w-5" />
|
|
{item.label}
|
|
</Link>
|
|
);
|
|
})}
|
|
</nav>
|
|
|
|
<div className="p-4 border-t border-[hsl(var(--border))]">
|
|
<div className="flex items-center gap-3 mb-3">
|
|
<div className="h-8 w-8 rounded-full bg-[hsl(var(--primary))] flex items-center justify-center text-[hsl(var(--primary-foreground))] text-sm font-medium">
|
|
{user?.name?.[0] || user?.email?.[0] || "U"}
|
|
</div>
|
|
<div className="flex-1 min-w-0">
|
|
<p className="text-sm font-medium truncate">{user?.name || user?.email}</p>
|
|
<p className="text-xs text-[hsl(var(--muted-foreground))] truncate">
|
|
{user?.role}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
<Button
|
|
variant="ghost"
|
|
className="w-full justify-start"
|
|
onClick={handleLogout}
|
|
>
|
|
<LogOut className="h-4 w-4 mr-2" />
|
|
Sign out
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</aside>
|
|
|
|
{/* Overlay for mobile */}
|
|
{sidebarOpen && (
|
|
<div
|
|
className="fixed inset-0 bg-black/50 z-40 lg:hidden"
|
|
onClick={() => setSidebarOpen(false)}
|
|
/>
|
|
)}
|
|
|
|
{/* Main content */}
|
|
<main className="flex-1 p-6 lg:p-8">{children}</main>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|