Add VPN configuration tools and agent documentation
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>
This commit is contained in:
@@ -0,0 +1,44 @@
|
||||
import { ButtonHTMLAttributes, forwardRef } from "react";
|
||||
import { cn } from "../lib/utils";
|
||||
|
||||
export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
variant?: "default" | "destructive" | "outline" | "secondary" | "ghost" | "link";
|
||||
size?: "default" | "sm" | "lg" | "icon";
|
||||
}
|
||||
|
||||
const Button = forwardRef<HTMLButtonElement, ButtonProps>(
|
||||
({ className, variant = "default", size = "default", ...props }, ref) => {
|
||||
return (
|
||||
<button
|
||||
className={cn(
|
||||
"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50",
|
||||
{
|
||||
"bg-[hsl(var(--primary))] text-[hsl(var(--primary-foreground))] shadow hover:bg-[hsl(var(--primary))]/90":
|
||||
variant === "default",
|
||||
"bg-[hsl(var(--destructive))] text-[hsl(var(--destructive-foreground))] shadow-sm hover:bg-[hsl(var(--destructive))]/90":
|
||||
variant === "destructive",
|
||||
"border border-[hsl(var(--input))] bg-transparent shadow-sm hover:bg-[hsl(var(--accent))] hover:text-[hsl(var(--accent-foreground))]":
|
||||
variant === "outline",
|
||||
"bg-[hsl(var(--secondary))] text-[hsl(var(--secondary-foreground))] shadow-sm hover:bg-[hsl(var(--secondary))]/80":
|
||||
variant === "secondary",
|
||||
"hover:bg-[hsl(var(--accent))] hover:text-[hsl(var(--accent-foreground))]":
|
||||
variant === "ghost",
|
||||
"text-[hsl(var(--primary))] underline-offset-4 hover:underline": variant === "link",
|
||||
},
|
||||
{
|
||||
"h-9 px-4 py-2": size === "default",
|
||||
"h-8 rounded-md px-3 text-xs": size === "sm",
|
||||
"h-10 rounded-md px-8": size === "lg",
|
||||
"h-9 w-9": size === "icon",
|
||||
},
|
||||
className
|
||||
)}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
);
|
||||
Button.displayName = "Button";
|
||||
|
||||
export { Button };
|
||||
@@ -0,0 +1,50 @@
|
||||
import { HTMLAttributes, forwardRef } from "react";
|
||||
import { cn } from "../lib/utils";
|
||||
|
||||
const Card = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(
|
||||
({ className, ...props }, ref) => (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"rounded-xl border border-[hsl(var(--border))] bg-[hsl(var(--card))] text-[hsl(var(--card-foreground))] shadow",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
);
|
||||
Card.displayName = "Card";
|
||||
|
||||
const CardHeader = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(
|
||||
({ className, ...props }, ref) => (
|
||||
<div ref={ref} className={cn("flex flex-col space-y-1.5 p-6", className)} {...props} />
|
||||
)
|
||||
);
|
||||
CardHeader.displayName = "CardHeader";
|
||||
|
||||
const CardTitle = forwardRef<HTMLParagraphElement, HTMLAttributes<HTMLHeadingElement>>(
|
||||
({ className, ...props }, ref) => (
|
||||
<h3
|
||||
ref={ref}
|
||||
className={cn("font-semibold leading-none tracking-tight", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
);
|
||||
CardTitle.displayName = "CardTitle";
|
||||
|
||||
const CardDescription = forwardRef<HTMLParagraphElement, HTMLAttributes<HTMLParagraphElement>>(
|
||||
({ className, ...props }, ref) => (
|
||||
<p ref={ref} className={cn("text-sm text-[hsl(var(--muted-foreground))]", className)} {...props} />
|
||||
)
|
||||
);
|
||||
CardDescription.displayName = "CardDescription";
|
||||
|
||||
const CardContent = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(
|
||||
({ className, ...props }, ref) => (
|
||||
<div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
|
||||
)
|
||||
);
|
||||
CardContent.displayName = "CardContent";
|
||||
|
||||
export { Card, CardHeader, CardTitle, CardDescription, CardContent };
|
||||
@@ -0,0 +1,21 @@
|
||||
import { InputHTMLAttributes, forwardRef } from "react";
|
||||
import { cn } from "../lib/utils";
|
||||
|
||||
export interface InputProps extends InputHTMLAttributes<HTMLInputElement> {}
|
||||
|
||||
const Input = forwardRef<HTMLInputElement, InputProps>(({ className, type, ...props }, ref) => {
|
||||
return (
|
||||
<input
|
||||
type={type}
|
||||
className={cn(
|
||||
"flex h-9 w-full rounded-md border border-[hsl(var(--input))] bg-transparent px-3 py-1 text-sm shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-[hsl(var(--muted-foreground))] focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-[hsl(var(--ring))] disabled:cursor-not-allowed disabled:opacity-50",
|
||||
className
|
||||
)}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
});
|
||||
Input.displayName = "Input";
|
||||
|
||||
export { Input };
|
||||
112
projects/msp-tools/guru-rmm/dashboard/src/components/Layout.tsx
Normal file
112
projects/msp-tools/guru-rmm/dashboard/src/components/Layout.tsx
Normal file
@@ -0,0 +1,112 @@
|
||||
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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user